LCOV - code coverage report
Current view: top level - flamenco/runtime/sysvar - fd_sysvar_clock.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 188 0.0 %
Date: 2025-09-17 04:38:03 Functions: 0 7 0.0 %

          Line data    Source code
       1             : #include "fd_sysvar.h"
       2             : #include "fd_sysvar_clock.h"
       3             : #include "fd_sysvar_epoch_schedule.h"
       4             : #include "fd_sysvar_rent.h"
       5             : #include "../fd_acc_mgr.h"
       6             : #include "../fd_system_ids.h"
       7             : 
       8             : /* https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/runtime/src/stake_weighted_timestamp.rs#L14 */
       9           0 : #define MAX_ALLOWABLE_DRIFT_FAST ( 25 )
      10             : 
      11             : /* https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/runtime/src/stake_weighted_timestamp.rs#L16 */
      12           0 : #define MAX_ALLOWABLE_DRIFT_SLOW ( 150 )
      13             : 
      14             : /* Do all intermediate calculations at nanosecond precision, to mirror Solana's behaviour. */
      15           0 : #define NS_IN_S ((long)1e9)
      16             : 
      17             : /* The target tick duration, derived from the target tick rate.
      18             :  https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/sdk/src/poh_config.rs#L32
      19             :   */
      20             : #define DEFAULT_TARGET_TICK_DURATION_NS ( NS_IN_S / FD_SYSVAR_CLOCK_DEFAULT_HASHES_PER_TICK )
      21             : 
      22             : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2110-L2117 */
      23             : static inline long
      24           0 : unix_timestamp_from_genesis( fd_bank_t * bank ) {
      25             :   /* TODO: genesis_creation_time needs to be a long in the bank. */
      26           0 :   return fd_long_sat_add(
      27           0 :       (long)fd_bank_genesis_creation_time_get( bank ),
      28           0 :       (long)( fd_uint128_sat_mul( fd_bank_slot_get( bank ), fd_bank_ns_per_slot_get( bank ) ) / NS_IN_S ) );
      29           0 : }
      30             : 
      31             : void
      32             : fd_sysvar_clock_write( fd_exec_slot_ctx_t *    slot_ctx,
      33           0 :                        fd_sol_sysvar_clock_t * clock ) {
      34           0 :   ulong sz = fd_sol_sysvar_clock_size( clock );
      35           0 :   uchar enc[sz];
      36           0 :   memset( enc, 0, sz );
      37           0 :   fd_bincode_encode_ctx_t ctx;
      38           0 :   ctx.data = enc;
      39           0 :   ctx.dataend = enc + sz;
      40           0 :   if( fd_sol_sysvar_clock_encode( clock, &ctx ) )
      41           0 :     FD_LOG_ERR(("fd_sol_sysvar_clock_encode failed"));
      42             : 
      43           0 :   fd_sysvar_account_update( slot_ctx, &fd_sysvar_clock_id, enc, sz );
      44           0 : }
      45             : 
      46             : 
      47             : fd_sol_sysvar_clock_t *
      48             : fd_sysvar_clock_read( fd_funk_t *             funk,
      49             :                       fd_funk_txn_t *         funk_txn,
      50           0 :                       fd_sol_sysvar_clock_t * clock ) {
      51           0 :   FD_TXN_ACCOUNT_DECL( acc );
      52           0 :   int rc = fd_txn_account_init_from_funk_readonly( acc, &fd_sysvar_clock_id, funk, funk_txn );
      53           0 :   if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS ) ) {
      54           0 :     return NULL;
      55           0 :   }
      56             : 
      57             :   /* This check is needed as a quirk of the fuzzer. If a sysvar account
      58             :      exists in the accounts database, but doesn't have any lamports,
      59             :      this means that the account does not exist. This wouldn't happen
      60             :      in a real execution environment. */
      61           0 :   if( FD_UNLIKELY( fd_txn_account_get_lamports( acc )==0UL ) ) {
      62           0 :     return NULL;
      63           0 :   }
      64             : 
      65           0 :   return fd_bincode_decode_static(
      66           0 :       sol_sysvar_clock, clock,
      67           0 :       fd_txn_account_get_data( acc ),
      68           0 :       fd_txn_account_get_data_len( acc ),
      69           0 :       &err );
      70           0 : }
      71             : 
      72             : void
      73           0 : fd_sysvar_clock_init( fd_exec_slot_ctx_t * slot_ctx ) {
      74           0 :   long timestamp = unix_timestamp_from_genesis( slot_ctx->bank );
      75             : 
      76           0 :   fd_sol_sysvar_clock_t clock = {
      77           0 :     .slot                  = fd_bank_slot_get( slot_ctx->bank ),
      78           0 :     .epoch                 = 0,
      79           0 :     .epoch_start_timestamp = timestamp,
      80           0 :     .leader_schedule_epoch = 1,
      81           0 :     .unix_timestamp        = timestamp,
      82           0 :   };
      83           0 :   fd_sysvar_clock_write( slot_ctx, &clock );
      84           0 : }
      85             : 
      86           0 : #define CIDX_T ulong
      87             : #define VAL_T  long
      88             : struct stake_ts_ele {
      89             :   CIDX_T parent_cidx;
      90             :   CIDX_T left_cidx;
      91             :   CIDX_T right_cidx;
      92             :   CIDX_T prio_cidx;
      93             :   VAL_T timestamp;
      94             :   uint128 stake;
      95             : };
      96             : 
      97             : typedef struct stake_ts_ele stake_ts_ele_t;
      98             : 
      99             : #define POOL_NAME  stake_ts_pool
     100           0 : #define POOL_T     stake_ts_ele_t
     101             : #define POOL_IDX_T CIDX_T
     102           0 : #define POOL_NEXT  parent_cidx
     103             : #include "../../../util/tmpl/fd_pool.c"
     104             : 
     105           0 : FD_FN_CONST static inline int valcmp (VAL_T a, VAL_T b) {
     106           0 :   int val = (a < b) ? -1 : 1;
     107           0 :   return (a == b) ? 0 : val;
     108           0 : }
     109             : 
     110             : #define TREAP_NAME       stake_ts_treap
     111             : #define TREAP_T          stake_ts_ele_t
     112             : #define TREAP_QUERY_T    VAL_T
     113           0 : #define TREAP_CMP(q,e)   valcmp(q, e->timestamp)
     114           0 : #define TREAP_LT(e0,e1)  (((VAL_T)((e0)->timestamp)) < ((VAL_T)((e1)->timestamp)))
     115           0 : #define TREAP_IDX_T      CIDX_T
     116           0 : #define TREAP_PARENT     parent_cidx
     117           0 : #define TREAP_LEFT       left_cidx
     118           0 : #define TREAP_RIGHT      right_cidx
     119           0 : #define TREAP_PRIO       prio_cidx
     120             : #define TREAP_IMPL_STYLE 0
     121             : #include "../../../util/tmpl/fd_treap.c"
     122             : 
     123             : /* get_timestamp_estimate calculates a timestamp estimate.  Does not
     124             :    modify the slot context.  Walks all cached vote accounts (from the
     125             :    "bank") and calculates a unix timestamp estimate. Returns the
     126             :    timestamp estimate.  spad is used for scratch allocations (allocates
     127             :    a treap of size FD_SYSVAR_CLOCK_STAKE_WEIGHTS_MAX). Crashes the
     128             :    process with FD_LOG_ERR on failure (e.g. too many vote accounts).
     129             : 
     130             :   https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2563-L2601 */
     131             : long
     132             : get_timestamp_estimate( fd_bank_t *             bank,
     133             :                         fd_sol_sysvar_clock_t * clock,
     134           0 :                         fd_spad_t *             spad ) {
     135           0 :   FD_SPAD_FRAME_BEGIN( spad ) {
     136             : 
     137           0 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
     138           0 :   ulong                       slot_duration  = (ulong)fd_bank_ns_per_slot_get( bank );
     139           0 :   ulong                       current_slot   = fd_bank_slot_get( bank );
     140             : 
     141             :   /* Set up a temporary treap, pool, and rng (required for treap prio).
     142             :      This is to establish a mapping between each vote timestamp and the
     143             :      amount of stake associated with each timestamp. */
     144           0 :   stake_ts_treap_t   _treap[1];
     145           0 :   stake_ts_treap_t * treap    = stake_ts_treap_join( stake_ts_treap_new( _treap, FD_RUNTIME_MAX_VOTE_ACCOUNTS ) );
     146           0 :   uchar *            pool_mem = fd_spad_alloc( spad, stake_ts_pool_align(), stake_ts_pool_footprint( FD_RUNTIME_MAX_VOTE_ACCOUNTS ) );
     147           0 :   stake_ts_ele_t *   pool     = stake_ts_pool_join( stake_ts_pool_new( pool_mem, FD_RUNTIME_MAX_VOTE_ACCOUNTS ) );
     148           0 :   uint               txn_cnt  = (uint)fd_bank_transaction_count_get( bank );
     149             : 
     150           0 :   fd_rng_t           _rng[1];
     151           0 :   fd_rng_t *         rng      = fd_rng_join( fd_rng_new( _rng, txn_cnt, 0UL ) );
     152             : 
     153             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L41 */
     154           0 :   uint128 total_stake = 0UL;
     155             : 
     156             :   /* A timestamp estimate is calculated at every slot using the most
     157             :      recent vote states of voting validators. This estimated is based on
     158             :      a stake weighted median using the stake as of the end of epoch E-2
     159             :      if we are currently in epoch E. We do not count vote accounts that
     160             :      have not voted in an epoch's worth of slots (432k). */
     161           0 :   fd_vote_states_t const * vote_states           = fd_bank_vote_states_locking_query( bank );
     162           0 :   fd_vote_states_t const * vote_states_prev_prev = fd_bank_vote_states_prev_prev_locking_query( bank );
     163             : 
     164           0 :   fd_vote_states_iter_t iter_[1];
     165           0 :   for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( iter_, vote_states );
     166           0 :        !fd_vote_states_iter_done( iter );
     167           0 :        fd_vote_states_iter_next( iter ) ) {
     168           0 :     fd_vote_state_ele_t const * vote_state      = fd_vote_states_iter_ele( iter );
     169           0 :     fd_vote_state_ele_t const * vote_state_prev = fd_vote_states_query_const( vote_states_prev_prev, &vote_state->vote_account );
     170           0 :     if( !vote_state_prev ) {
     171             :       /* Don't count vote accounts that didn't have stake at the end of
     172             :          epoch E-2. */
     173           0 :       continue;
     174           0 :     }
     175           0 :     ulong vote_stake     = vote_state_prev->stake;
     176           0 :     ulong vote_timestamp = (ulong)vote_state->last_vote_timestamp;
     177           0 :     ulong vote_slot      = vote_state->last_vote_slot;
     178             : 
     179             :     /* https://github.com/anza-xyz/agave/blob/v3.0.0/runtime/src/bank.rs#L2445 */
     180           0 :     ulong slot_delta;
     181           0 :     int err = fd_ulong_checked_sub( current_slot, vote_slot, &slot_delta );
     182           0 :     if( FD_UNLIKELY( err ) ) {
     183           0 :       continue;
     184           0 :     }
     185             : 
     186             :     /* Don't count vote accounts that haven't voted in the past 432k
     187             :        slots (length of an epoch).
     188             :        https://github.com/anza-xyz/agave/blob/v3.0.0/runtime/src/bank.rs#L2446-L2447 */
     189           0 :     if( slot_delta>epoch_schedule->slots_per_epoch ) {
     190           0 :       continue;
     191           0 :     }
     192             : 
     193             :     /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L44-L45 */
     194           0 :     ulong offset   = fd_ulong_sat_mul(slot_duration, slot_delta);
     195           0 :     long  estimate = (long)vote_timestamp + (long)(offset / NS_IN_S);
     196             : 
     197             :     /* Get the vote account stake and upsert it to the treap.
     198             :        https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L46-L53 */
     199           0 :     ulong treap_idx = stake_ts_treap_idx_query( treap, estimate, pool );
     200           0 :     if ( FD_LIKELY( treap_idx < ULONG_MAX ) ) {
     201           0 :       pool[ treap_idx ].stake += vote_stake;
     202           0 :     } else {
     203           0 :       if( FD_UNLIKELY( stake_ts_pool_free( pool )==0UL ) ){
     204           0 :         FD_LOG_ERR(( "stake_ts_pool is empty" ));
     205           0 :       }
     206           0 :       ulong idx = stake_ts_pool_idx_acquire( pool );
     207           0 :       pool[ idx ].prio_cidx = fd_rng_ulong( rng );
     208           0 :       pool[ idx ].timestamp = estimate;
     209           0 :       pool[ idx ].stake     = vote_stake;
     210           0 :       stake_ts_treap_idx_insert( treap, idx, pool );
     211           0 :     }
     212             : 
     213             :     /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L54 */
     214           0 :     total_stake += vote_stake;
     215           0 :   }
     216           0 :   fd_bank_vote_states_end_locking_query( bank );
     217           0 :   fd_bank_vote_states_prev_prev_end_locking_query( bank );
     218             : 
     219             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L56-L58 */
     220           0 :   if( FD_UNLIKELY( total_stake==0UL ) ) {
     221           0 :     return 0L;
     222           0 :   }
     223             : 
     224             :   /* Populate estimate with the stake-weighted median timestamp.
     225             :      https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L59-L68 */
     226           0 :   uint128 stake_accumulator = 0;
     227           0 :   long    estimate          = 0L;
     228           0 :   for( stake_ts_treap_fwd_iter_t iter = stake_ts_treap_fwd_iter_init( treap, pool );
     229           0 :        !stake_ts_treap_fwd_iter_done( iter );
     230           0 :        iter = stake_ts_treap_fwd_iter_next( iter, pool ) ) {
     231           0 :     ulong idx         = stake_ts_treap_fwd_iter_idx( iter );
     232           0 :     stake_accumulator = fd_uint128_sat_add( stake_accumulator, pool[ idx ].stake );
     233           0 :     if( stake_accumulator>(total_stake/2UL) ) {
     234           0 :       estimate = pool[ idx ].timestamp;
     235           0 :       break;
     236           0 :     }
     237           0 :   }
     238             : 
     239           0 :   FD_LOG_DEBUG(( "stake weighted timestamp: %ld total stake %lu", estimate, (ulong)total_stake ));
     240             : 
     241           0 :   int const fix_estimate_into_u64 = FD_FEATURE_ACTIVE_BANK( bank, warp_timestamp_again );
     242             : 
     243             :   /* Bound estimate by `max_allowable_drift` since the start of the epoch
     244             :      https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L69-L99 */
     245           0 :   ulong epoch_start_slot      = fd_epoch_slot0( epoch_schedule, clock->epoch );
     246           0 :   long  epoch_start_timestamp = clock->epoch_start_timestamp;
     247             : 
     248             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L71-L72 */
     249           0 :   ulong poh_estimate_offset = fd_ulong_sat_mul( slot_duration, fd_ulong_sat_sub( current_slot, epoch_start_slot ) );
     250             : 
     251             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L73-L77 */
     252           0 :   ulong estimate_offset;
     253           0 :   if( fix_estimate_into_u64 ) {
     254           0 :     estimate_offset = fd_ulong_sat_mul( NS_IN_S, fd_ulong_sat_sub( (ulong)estimate, (ulong)epoch_start_timestamp ) );
     255           0 :   } else {
     256           0 :     estimate_offset = fd_ulong_sat_mul( NS_IN_S, (ulong)fd_long_sat_sub( estimate, epoch_start_timestamp ) );
     257           0 :   }
     258             : 
     259             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L78-L81 */
     260           0 :   ulong max_allowable_drift_fast = fd_ulong_sat_mul( poh_estimate_offset, MAX_ALLOWABLE_DRIFT_FAST ) / 100UL;
     261           0 :   ulong max_allowable_drift_slow = fd_ulong_sat_mul( poh_estimate_offset, MAX_ALLOWABLE_DRIFT_SLOW ) / 100UL;
     262           0 :   FD_LOG_DEBUG(( "poh offset %lu estimate %lu fast %lu slow %lu", poh_estimate_offset, estimate_offset, max_allowable_drift_fast, max_allowable_drift_slow ));
     263             : 
     264             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L82-L98 */
     265           0 :   if( estimate_offset>poh_estimate_offset && fd_ulong_sat_sub(estimate_offset, poh_estimate_offset)>max_allowable_drift_slow ) {
     266           0 :     estimate = fd_long_sat_add(
     267           0 :         epoch_start_timestamp,
     268           0 :         fd_long_sat_add( (long)poh_estimate_offset / NS_IN_S, (long)max_allowable_drift_slow / NS_IN_S ) );
     269           0 :   } else if( estimate_offset<poh_estimate_offset && fd_ulong_sat_sub(poh_estimate_offset, estimate_offset)>max_allowable_drift_fast ) {
     270           0 :     estimate = fd_long_sat_sub(
     271           0 :         fd_long_sat_add( epoch_start_timestamp, (long)poh_estimate_offset / NS_IN_S ),
     272           0 :         (long)max_allowable_drift_fast / NS_IN_S );
     273           0 :   }
     274             : 
     275           0 :   return estimate;
     276             : 
     277           0 :   } FD_SPAD_FRAME_END;
     278           0 : }
     279             : 
     280             : /* TODO: This function should be called from genesis bootup as well with
     281             :    parent_epoch = NULL
     282             :    https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2158-L2215 */
     283             : void
     284             : fd_sysvar_clock_update( fd_exec_slot_ctx_t * slot_ctx,
     285             :                         fd_spad_t *          spad,
     286           0 :                         ulong const *        parent_epoch ) {
     287           0 :   fd_sol_sysvar_clock_t clock_[1];
     288           0 :   fd_sol_sysvar_clock_t * clock = fd_sysvar_clock_read( slot_ctx->funk, slot_ctx->funk_txn, clock_ );
     289           0 :   if( FD_UNLIKELY( !clock ) ) FD_LOG_ERR(( "fd_sysvar_clock_read failed" ));
     290             : 
     291           0 :   fd_bank_t *                 bank           = slot_ctx->bank;
     292           0 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
     293           0 :   ulong                       current_slot   = fd_bank_slot_get( bank );
     294           0 :   ulong                       current_epoch  = fd_slot_to_epoch( epoch_schedule, current_slot, NULL );
     295             : 
     296             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2159 */
     297           0 :   long unix_timestamp = clock->unix_timestamp;
     298             : 
     299             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2175 */
     300           0 :   long ancestor_timestamp = clock->unix_timestamp;
     301             : 
     302             :   /* TODO: Are we handling slot 0 correctly?
     303             :      https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2176-L2183 */
     304           0 :   long timestamp_estimate = get_timestamp_estimate( bank, clock, spad );
     305             : 
     306             :   /* If the timestamp was successfully calculated, use it. It not keep the old one. */
     307           0 :   if( FD_LIKELY( timestamp_estimate!=0L ) ) {
     308           0 :     unix_timestamp = timestamp_estimate;
     309             : 
     310             :     /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2180-L2182 */
     311           0 :     if( timestamp_estimate<ancestor_timestamp ) {
     312           0 :       unix_timestamp = ancestor_timestamp;
     313           0 :     }
     314           0 :   }
     315             : 
     316             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2191-L2197 */
     317           0 :   long epoch_start_timestamp = ( parent_epoch!=NULL && *parent_epoch!=current_epoch ) ?
     318           0 :       unix_timestamp :
     319           0 :       clock->epoch_start_timestamp;
     320             : 
     321             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2198-L2201 */
     322           0 :   if( FD_UNLIKELY( current_slot==0UL ) ) {
     323           0 :     long timestamp_from_genesis = unix_timestamp_from_genesis( bank );
     324           0 :     unix_timestamp              = timestamp_from_genesis;
     325           0 :     epoch_start_timestamp       = timestamp_from_genesis;
     326           0 :   }
     327             : 
     328             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2202-L2208 */
     329           0 :   *clock = (fd_sol_sysvar_clock_t){
     330           0 :     .slot                  = current_slot,
     331           0 :     .epoch_start_timestamp = epoch_start_timestamp,
     332           0 :     .epoch                 = current_epoch,
     333           0 :     .leader_schedule_epoch = fd_slot_to_leader_schedule_epoch( epoch_schedule, current_slot ),
     334           0 :     .unix_timestamp        = unix_timestamp,
     335           0 :   };
     336             : 
     337             :   /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2209-L2214 */
     338           0 :   fd_sysvar_clock_write( slot_ctx, clock );
     339           0 : }

Generated by: LCOV version 1.14