LCOV - code coverage report
Current view: top level - flamenco/runtime/sysvar - fd_sysvar_recent_hashes.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 104 0.0 %
Date: 2025-07-01 05:00:49 Functions: 0 5 0.0 %

          Line data    Source code
       1             : #include <stdio.h>
       2             : #include "../fd_acc_mgr.h"
       3             : #include "../fd_hashes.h"
       4             : #include "fd_sysvar.h"
       5             : #include "../fd_runtime.h"
       6             : #include "../fd_system_ids.h"
       7             : #include "../context/fd_exec_slot_ctx.h"
       8             : 
       9           0 : #define FD_RECENT_BLOCKHASHES_ACCOUNT_MAX_SIZE  sizeof(ulong) + FD_RECENT_BLOCKHASHES_MAX_ENTRIES * (sizeof(fd_hash_t) + sizeof(ulong))
      10             : 
      11             : // run --ledger /home/jsiegel/test-ledger --db /home/jsiegel/funk --cmd accounts --accounts /home/jsiegel/test-ledger/accounts/ --pages 15 --index-max 120000000 --start-slot 2 --end-slot 2 --start-id 35 --end-id 37
      12             : // run --ledger /home/jsiegel/test-ledger --db /home/jsiegel/funk --cmd replay --pages 15 --index-max 120000000 --start-slot 0 --end-slot 3
      13             : 
      14             : // {meta = {write_version_obsolete = 137,
      15             : // data_len = 6008, pubkey = "\006\247\325\027\031,V\216\340\212\204_sҗ\210\317\003\\1E\262\032\263D\330\006.\251@\000"}, info = {lamports = 42706560, rent_epoch = 0, owner = "\006\247\325\027\030u\367)\307=\223@\217!a \006~،v\340\214(\177\301\224`\000\000\000", executable = 0 '\000', padding = "K\000\f\376\177\000"}, hash = {value = "\302Q\316\035qTY\347\352]\260\335\213\224R\227ԯ\366R\273\063H\345֑c\377\207/k\275"}}
      16             : 
      17             : // owner:      Sysvar1111111111111111111111111111111111111 pubkey:      SysvarRecentB1ockHashes11111111111111111111 hash:     E5YSehyvJ7xXcNnQjWCH9UhMJ1dxDBJ1RuuPh1Y3RZgg file: /home/jsiegel/test-ledger/accounts//2.37
      18             : //   {blockhash = JCidNXtcMXMWQwMDM3ZQq5pxaw3hQpNbeHg1KcstjuF4,  fee_calculator={lamports_per_signature = 5000}}
      19             : //   {blockhash = GQN3oV8G1Ra3GCX76dE1YYJ6UjMyDreNCEWM4tZ39zj1,  fee_calculator={lamports_per_signature = 5000}}
      20             : //   {blockhash = Ha5DVgnD1xSA8oQc337jtA3atEfQ4TFX1ajeZG1Y2tUx,  fee_calculator={lamports_per_signature = 0}}
      21             : 
      22             : /* Skips fd_types encoding preflight checks and directly serializes the blockhash queue into a buffer representing
      23             :    account data for the recent blockhashes sysvar. */
      24             : 
      25             : static void
      26           0 : encode_rbh_from_blockhash_queue( fd_exec_slot_ctx_t * slot_ctx, uchar * enc ) {
      27           0 :   fd_block_hash_queue_global_t const * bhq = fd_bank_block_hash_queue_query( slot_ctx->bank );
      28             : 
      29           0 :   fd_hash_hash_age_pair_t_mapnode_t * ages_pool = fd_block_hash_queue_ages_pool_join( bhq );
      30           0 :   fd_hash_hash_age_pair_t_mapnode_t * ages_root = fd_block_hash_queue_ages_root_join( bhq );
      31             : 
      32           0 :   ulong queue_sz   = fd_hash_hash_age_pair_t_map_size( ages_pool, ages_root );
      33           0 :   ulong hashes_len = fd_ulong_min( queue_sz, FD_RECENT_BLOCKHASHES_MAX_ENTRIES );
      34           0 :   fd_memcpy( enc, &hashes_len, sizeof(ulong) );
      35           0 :   enc += sizeof(ulong);
      36             : 
      37             :   /* Iterate over blockhash queue and encode the recent blockhashes.
      38             :      We can do direct memcpying and avoid redundant checks from fd_types
      39             :      encoders since the enc buffer is already sized out to the
      40             :      worst-case bound. */
      41           0 :   fd_hash_hash_age_pair_t_mapnode_t const * nn;
      42           0 :   for( fd_hash_hash_age_pair_t_mapnode_t const * n = fd_hash_hash_age_pair_t_map_minimum_const( ages_pool, ages_root ); n; n = nn ) {
      43           0 :     nn = fd_hash_hash_age_pair_t_map_successor_const( ages_pool, n );
      44           0 :     ulong enc_idx = bhq->last_hash_index - n->elem.val.hash_index;
      45           0 :     if( enc_idx>=hashes_len ) {
      46           0 :       continue;
      47           0 :     }
      48           0 :     fd_hash_t hash = n->elem.key;
      49           0 :     ulong     lps  = n->elem.val.fee_calculator.lamports_per_signature;
      50             : 
      51           0 :     fd_memcpy( enc + enc_idx * (FD_HASH_FOOTPRINT + sizeof(ulong)), &hash, FD_HASH_FOOTPRINT );
      52           0 :     fd_memcpy( enc + enc_idx * (FD_HASH_FOOTPRINT + sizeof(ulong)) + sizeof(fd_hash_t), &lps, sizeof(ulong) );
      53           0 :   }
      54           0 : }
      55             : 
      56             : // https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/sdk/program/src/fee_calculator.rs#L110
      57             : void
      58             : fd_sysvar_recent_hashes_init( fd_exec_slot_ctx_t * slot_ctx,
      59           0 :                               fd_spad_t *          runtime_spad ) {
      60             : 
      61           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
      62             : 
      63           0 :   if( slot_ctx->slot != 0 ) {
      64           0 :     return;
      65           0 :   }
      66             : 
      67           0 :   ulong   sz  = FD_RECENT_BLOCKHASHES_ACCOUNT_MAX_SIZE;
      68           0 :   uchar * enc = fd_spad_alloc( runtime_spad, FD_SPAD_ALIGN, sz );
      69           0 :   fd_memset( enc, 0, sz );
      70           0 :   encode_rbh_from_blockhash_queue( slot_ctx, enc );
      71           0 :   fd_sysvar_set( slot_ctx->bank, slot_ctx->funk, slot_ctx->funk_txn, &fd_sysvar_owner_id, &fd_sysvar_recent_block_hashes_id, enc, sz, slot_ctx->slot );
      72             : 
      73           0 :   } FD_SPAD_FRAME_END;
      74           0 : }
      75             : 
      76             : // https://github.com/anza-xyz/agave/blob/e8750ba574d9ac7b72e944bc1227dc7372e3a490/accounts-db/src/blockhash_queue.rs#L113
      77             : static void
      78           0 : register_blockhash( fd_exec_slot_ctx_t * slot_ctx, fd_hash_t const * hash ) {
      79             : 
      80           0 :   fd_block_hash_queue_global_t *      bhq       = fd_bank_block_hash_queue_modify( slot_ctx->bank );
      81           0 :   fd_hash_hash_age_pair_t_mapnode_t * ages_pool = fd_block_hash_queue_ages_pool_join( bhq );
      82           0 :   fd_hash_hash_age_pair_t_mapnode_t * ages_root = fd_block_hash_queue_ages_root_join( bhq );
      83           0 :   bhq->last_hash_index++;
      84           0 :   if( fd_hash_hash_age_pair_t_map_size( ages_pool, ages_root ) >= bhq->max_age ) {
      85           0 :     fd_hash_hash_age_pair_t_mapnode_t * nn;
      86           0 :     for( fd_hash_hash_age_pair_t_mapnode_t * n = fd_hash_hash_age_pair_t_map_minimum( ages_pool, ages_root ); n; n = nn ) {
      87           0 :       nn = fd_hash_hash_age_pair_t_map_successor( ages_pool, n );
      88             :       /* NOTE: Yes, this check is incorrect. It should be >= which caps the blockhash queue at max_age
      89             :          entries, but instead max_age + 1 entries are allowed to exist in the queue at once. This mimics
      90             :          Agave to stay conformant with their implementation.
      91             :          https://github.com/anza-xyz/agave/blob/e8750ba574d9ac7b72e944bc1227dc7372e3a490/accounts-db/src/blockhash_queue.rs#L109 */
      92           0 :       if( bhq->last_hash_index - n->elem.val.hash_index > bhq->max_age ) {
      93           0 :         fd_hash_hash_age_pair_t_map_remove( ages_pool, &ages_root, n );
      94           0 :         fd_hash_hash_age_pair_t_map_release( ages_pool, n );
      95           0 :       }
      96           0 :     }
      97           0 :   }
      98             : 
      99           0 :   fd_hash_hash_age_pair_t_mapnode_t * node = fd_hash_hash_age_pair_t_map_acquire( ages_pool );
     100           0 :   node->elem = (fd_hash_hash_age_pair_t){
     101           0 :     .key = *hash,
     102           0 :     .val = (fd_hash_age_t){ .hash_index = bhq->last_hash_index, .fee_calculator = (fd_fee_calculator_t){ .lamports_per_signature = fd_bank_lamports_per_signature_get( slot_ctx->bank ) }, .timestamp = (ulong)fd_log_wallclock() }
     103           0 :   };
     104             :   // https://github.com/anza-xyz/agave/blob/e8750ba574d9ac7b72e944bc1227dc7372e3a490/accounts-db/src/blockhash_queue.rs#L121-L128
     105           0 :   fd_hash_hash_age_pair_t_map_insert( ages_pool, &ages_root, node );
     106             :   // https://github.com/anza-xyz/agave/blob/e8750ba574d9ac7b72e944bc1227dc7372e3a490/accounts-db/src/blockhash_queue.rs#L130
     107           0 :   fd_hash_t * last_hash = fd_block_hash_queue_last_hash_join( bhq );
     108           0 :   fd_memcpy( last_hash, hash, sizeof(fd_hash_t) );
     109             : 
     110           0 :   fd_block_hash_queue_ages_pool_update( bhq, ages_pool );
     111           0 :   fd_block_hash_queue_ages_root_update( bhq, ages_root );
     112           0 : }
     113             : 
     114             : /* This implementation is more consistent with Agave's bank implementation for updating the block hashes sysvar:
     115             :    1. Update the block hash queue with the latest poh
     116             :    2. Take the first 150 blockhashes from the queue (or fewer if there are)
     117             :    3. Manually serialize the recent blockhashes
     118             :    4. Set the sysvar account with the new data */
     119             : void
     120           0 : fd_sysvar_recent_hashes_update( fd_exec_slot_ctx_t * slot_ctx, fd_spad_t * runtime_spad ) {
     121           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
     122             :   /* Update the blockhash queue */
     123             : 
     124           0 :   register_blockhash( slot_ctx, fd_bank_poh_query( slot_ctx->bank ) );
     125             : 
     126             :   /* Derive the new sysvar recent blockhashes from the blockhash queue */
     127           0 :   ulong   sz        = FD_RECENT_BLOCKHASHES_ACCOUNT_MAX_SIZE;
     128           0 :   uchar * enc       = fd_spad_alloc( runtime_spad, FD_SPAD_ALIGN, sz );
     129           0 :   uchar * enc_start = enc;
     130           0 :   fd_memset( enc, 0, sz );
     131             : 
     132             :   /* Encode the recent blockhashes */
     133           0 :   encode_rbh_from_blockhash_queue( slot_ctx, enc );
     134             : 
     135             :   /* Set the sysvar from the encoded data */
     136           0 :   fd_sysvar_set( slot_ctx->bank,
     137           0 :                  slot_ctx->funk,
     138           0 :                  slot_ctx->funk_txn,
     139           0 :                  &fd_sysvar_owner_id,
     140           0 :                  &fd_sysvar_recent_block_hashes_id,
     141           0 :                  enc_start,
     142           0 :                  sz,
     143           0 :                  slot_ctx->slot );
     144           0 :   } FD_SPAD_FRAME_END;
     145           0 : }
     146             : 
     147             : fd_recent_block_hashes_global_t *
     148           0 : fd_sysvar_recent_hashes_read( fd_funk_t * funk, fd_funk_txn_t * funk_txn, fd_spad_t * spad ) {
     149           0 :   FD_TXN_ACCOUNT_DECL( acc );
     150           0 :   int err = fd_txn_account_init_from_funk_readonly( acc, &fd_sysvar_recent_block_hashes_id, funk, funk_txn );
     151           0 :   if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) )
     152           0 :     return NULL;
     153             : 
     154           0 :   fd_bincode_decode_ctx_t ctx = {
     155           0 :     .data    = acc->vt->get_data( acc ),
     156           0 :     .dataend = acc->vt->get_data( acc ) + acc->vt->get_data_len( acc ),
     157           0 :   };
     158             : 
     159             :   /* This check is needed as a quirk of the fuzzer. If a sysvar account
     160             :      exists in the accounts database, but doesn't have any lamports,
     161             :      this means that the account does not exist. This wouldn't happen
     162             :      in a real execution environment. */
     163           0 :   if( FD_UNLIKELY( acc->vt->get_lamports( acc ) == 0UL ) ) {
     164           0 :     return NULL;
     165           0 :   }
     166             : 
     167           0 :   ulong total_sz = 0;
     168           0 :   err = fd_recent_block_hashes_decode_footprint( &ctx, &total_sz );
     169           0 :   if( FD_UNLIKELY( err ) ) {
     170           0 :     return NULL;
     171           0 :   }
     172             : 
     173           0 :   uchar * mem = fd_spad_alloc( spad, fd_recent_block_hashes_align(), total_sz );
     174           0 :   if( FD_UNLIKELY( !mem ) ) {
     175           0 :     FD_LOG_CRIT(( "fd_spad_alloc failed" ));
     176           0 :   }
     177             : 
     178             :   /* This would never happen in a real cluster, this is a workaround
     179             :      for fuzz-generated cases where sysvar accounts are not funded. */
     180           0 :   if( FD_UNLIKELY( acc->vt->get_lamports( acc ) == 0 ) ) {
     181           0 :     return NULL;
     182           0 :   }
     183             : 
     184           0 :   return fd_recent_block_hashes_decode_global( mem, &ctx );
     185           0 : }

Generated by: LCOV version 1.14