LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_alut.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 123 130 94.6 %
Date: 2026-03-19 18:19:27 Functions: 8 32 25.0 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_flamenco_runtime_fd_alut_h
       2             : #define HEADER_fd_src_flamenco_runtime_fd_alut_h
       3             : 
       4             : /* fd_alut.h provides APIs for interpreting Solana address lookup table
       5             :    usages.
       6             : 
       7             :    https://solana.com/de/developers/guides/advanced/lookup-tables */
       8             : 
       9             : #include "../../ballet/txn/fd_txn.h"
      10             : #include "../../ballet/base58/fd_base58.h"
      11             : #include "fd_runtime_err.h"
      12             : #include "fd_system_ids.h"
      13             : #include "sysvar/fd_sysvar_slot_hashes.h"
      14             : 
      15        5012 : #define FD_ADDRLUT_STATUS_ACTIVATED    (0)
      16        2512 : #define FD_ADDRLUT_STATUS_DEACTIVATING (1)
      17           8 : #define FD_ADDRLUT_STATUS_DEACTIVATED  (2)
      18             : 
      19             : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L19 */
      20        5026 : #define FD_LOOKUP_TABLE_META_SIZE      (56)
      21             : #define FD_ADDRLUT_MAX_ENTRIES         FD_SYSVAR_SLOT_HASHES_CAP
      22             : 
      23             : /* fd_alut_interp_t interprets indirect account references of a txn. */
      24             : 
      25             : struct fd_alut_interp {
      26             :   fd_acct_addr_t *       out_accts_alt;
      27             : 
      28             :   fd_txn_t const *       txn;
      29             :   uchar const *          txn_payload;
      30             :   fd_slot_hash_t const * hashes; /* deque */
      31             :   ulong                  slot;
      32             : 
      33             :   ulong                  alut_idx;
      34             :   ulong                  ro_indir_cnt;
      35             :   ulong                  rw_indir_cnt;
      36             : };
      37             : 
      38             : typedef struct fd_alut_interp fd_alut_interp_t;
      39             : 
      40             : FD_PROTOTYPES_BEGIN
      41             : 
      42             : /* fd_alut_slot_hashes_position, fd_alut_status, and fd_alut_is_active
      43             :    are all helper methods for determining the number of active addresses
      44             :    in an address lookup table account. */
      45             : 
      46             : 
      47             : /* Logic here is copied from slice::binary_search_by() in Rust. While
      48             :    not fully optimized, it aims to achieve fuzzing conformance for both
      49             :    sorted and unsorted inputs. */
      50             : FD_FN_UNUSED static ulong
      51             : fd_alut_slot_hashes_position( fd_slot_hash_t const * hashes, /* deque */
      52           5 :                               ulong                  slot ) {
      53           5 :   ulong size = deq_fd_slot_hash_t_cnt( hashes );
      54           5 :   if( FD_UNLIKELY( size==0UL ) ) return ULONG_MAX;
      55             : 
      56           5 :   ulong base = 0UL;
      57          11 :   while( size>1UL ) {
      58           6 :     ulong half = size / 2UL;
      59           6 :     ulong mid = base + half;
      60           6 :     ulong mid_slot = deq_fd_slot_hash_t_peek_index_const( hashes, mid )->slot;
      61           6 :     base = (slot>mid_slot) ? base : mid;
      62           6 :     size -= half;
      63           6 :   }
      64             : 
      65           5 :   return deq_fd_slot_hash_t_peek_index_const( hashes, base )->slot==slot ? base : ULONG_MAX;
      66           5 : }
      67             : 
      68             : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L81-L104 */
      69             : FD_FN_UNUSED static uchar
      70             : fd_alut_status( fd_lookup_table_meta_t const * state,
      71             :                 ulong                          current_slot,
      72        2513 :                 fd_slot_hash_t const *         slot_hashes /* deque */ ) {
      73        2513 :   if( state->deactivation_slot==ULONG_MAX ) {
      74        2506 :     return FD_ADDRLUT_STATUS_ACTIVATED;
      75        2506 :   }
      76             : 
      77           7 :   if( state->deactivation_slot==current_slot ) {
      78           2 :     return FD_ADDRLUT_STATUS_DEACTIVATING;
      79           2 :   }
      80             : 
      81           5 :   ulong slot_hash_position = fd_alut_slot_hashes_position( slot_hashes, state->deactivation_slot );
      82           5 :   if( slot_hash_position!=ULONG_MAX ) {
      83           1 :     return FD_ADDRLUT_STATUS_DEACTIVATING;
      84           1 :   }
      85             : 
      86           4 :   return FD_ADDRLUT_STATUS_DEACTIVATED;
      87           5 : }
      88             : 
      89             : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L72-L78 */
      90             : FD_FN_UNUSED static uchar
      91             : fd_alut_is_active( fd_address_lookup_table_t const * self,
      92             :                       ulong                          current_slot,
      93        2513 :                       fd_slot_hash_t const *         slot_hashes  /* deque */ ) {
      94        2513 :   uchar status = fd_alut_status( &self->meta, current_slot, slot_hashes );
      95        2513 :   switch( status ) {
      96        2506 :     case FD_ADDRLUT_STATUS_ACTIVATED:
      97        2509 :     case FD_ADDRLUT_STATUS_DEACTIVATING:
      98        2509 :       return 1;
      99           4 :     case FD_ADDRLUT_STATUS_DEACTIVATED:
     100           4 :       return 0;
     101           0 :     default:
     102           0 :       FD_LOG_CRIT(( "invalid lut status %d", status ));
     103        2513 :   }
     104        2513 : }
     105             : 
     106             : /* fd_alut_active_addresses_len returns the number of active addresses
     107             :    in an address lookup table account.
     108             :    https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L142-L164 */
     109             : FD_FN_UNUSED static int
     110             : fd_alut_active_addresses_len( fd_address_lookup_table_t * self,
     111             :                               ulong                       current_slot,
     112             :                               fd_slot_hash_t const *      slot_hashes, /* deque */
     113             :                               ulong                       addresses_len,
     114        2513 :                               ulong *                     active_addresses_len /* out */ ) {
     115        2513 :   if( FD_UNLIKELY( !fd_alut_is_active( self, current_slot, slot_hashes ) ) ) {
     116           4 :     return FD_RUNTIME_TXN_ERR_ADDRESS_LOOKUP_TABLE_NOT_FOUND;
     117           4 :   }
     118             : 
     119        2509 :   *active_addresses_len = ( current_slot > self->meta.last_extended_slot )
     120        2509 :       ? addresses_len
     121        2509 :       : self->meta.last_extended_slot_start_index;
     122             : 
     123        2509 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     124        2513 : }
     125             : 
     126             : /* fd_alut_interp_new creates a new ALUT interpreter.
     127             :    Will write indirectly referenced addresses to out_addrs.
     128             :    txn_payload points to a valid serialized transaction, txn points to
     129             :    the associated transaction descriptor.  alut_interp retains a write
     130             :    interest in out_addrs, and a read interest in txn, txn_payload, and
     131             :    hashes until it is destroyed. */
     132             : 
     133             : FD_FN_UNUSED static fd_alut_interp_t *
     134             : fd_alut_interp_new( fd_alut_interp_t *     interp,
     135             :                     fd_acct_addr_t *       out_addrs,
     136             :                     fd_txn_t const *       txn,
     137             :                     uchar const *          txn_payload,
     138             :                     fd_slot_hash_t const * hashes, /* deque */
     139        4979 :                     ulong                  slot ) {
     140        4979 :   *interp = (fd_alut_interp_t){
     141        4979 :     .out_accts_alt = out_addrs,
     142        4979 :     .txn           = txn,
     143        4979 :     .txn_payload   = txn_payload,
     144        4979 :     .hashes        = hashes,
     145        4979 :     .slot          = slot,
     146        4979 :     .alut_idx      = 0UL,
     147        4979 :     .ro_indir_cnt  = 0UL,
     148        4979 :     .rw_indir_cnt  = 0UL
     149        4979 :   };
     150        4979 :   return interp;
     151        4979 : }
     152             : 
     153             : /* fd_alut_interp_delete destroys an ALUT interpreter object.  Releases
     154             :    references to out_addrs, txn, and txn_payload. */
     155             : 
     156             : FD_FN_UNUSED static void *
     157        4981 : fd_alut_interp_delete( fd_alut_interp_t * interp ) {
     158        4981 :   return interp;
     159        4981 : }
     160             : 
     161             : static inline int
     162        2581 : fd_alut_interp_done( fd_alut_interp_t const * interp ) {
     163        2581 :   return interp->alut_idx >= interp->txn->addr_table_lookup_cnt;
     164        2581 : }
     165             : 
     166             : /* fd_alut_interp_next resolves a subset of a txn's indirect account
     167             :    references.  Resolves all addresses that are specified in the ALUT
     168             :    at index alut_idx.  Returns one of:
     169             :    - FD_RUNTIME_EXECUTE_SUCCESS
     170             :    - FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_OWNER
     171             :    - FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA
     172             :    - FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX
     173             :    - FD_RUNTIME_TXN_ERR_ADDRESS_LOOKUP_TABLE_NOT_FOUND */
     174             : 
     175             : FD_FN_UNUSED static int
     176             : fd_alut_interp_next( fd_alut_interp_t * interp,
     177             :                      void const *       alut_addr,
     178             :                      void const *       alut_owner,
     179             :                      uchar const *      alut_data,
     180        2581 :                      ulong              alut_data_sz ) {
     181        2581 :   if( FD_UNLIKELY( fd_alut_interp_done( interp ) ) ) FD_LOG_CRIT(( "invariant violation" ));
     182        2581 :   fd_acct_addr_t alut_addr_expected =
     183        2581 :       FD_LOAD( fd_acct_addr_t, interp->txn_payload+fd_txn_get_address_tables_const( interp->txn )[ interp->alut_idx ].addr_off );
     184        2581 :   if( FD_UNLIKELY( !fd_memeq( alut_addr, &alut_addr_expected, sizeof(fd_acct_addr_t) ) ) ) {
     185           0 :     FD_BASE58_ENCODE_32_BYTES( alut_addr,            alut_addr_b58          );
     186           0 :     FD_BASE58_ENCODE_32_BYTES( alut_addr_expected.b, alut_addr_expected_b58 );
     187           0 :     FD_LOG_CRIT(( "expected address lookup table account %s but got %s",
     188           0 :                   alut_addr_expected_b58, alut_addr_b58 ));
     189           0 :   }
     190        2581 :   fd_txn_acct_addr_lut_t const * addr_lut =
     191        2581 :       &fd_txn_get_address_tables_const( interp->txn )[ interp->alut_idx ];
     192             : 
     193             :   /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L96-L114 */
     194        2581 :   if( FD_UNLIKELY( !fd_memeq( alut_owner, fd_solana_address_lookup_table_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
     195          29 :     return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_OWNER;
     196          29 :   }
     197             : 
     198             :   /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L205-L209 */
     199        2552 :   if( FD_UNLIKELY( alut_data_sz < FD_LOOKUP_TABLE_META_SIZE ) ) {
     200          35 :     return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
     201          35 :   }
     202             : 
     203             :   /* https://github.com/anza-xyz/agave/blob/574bae8fefc0ed256b55340b9d87b7689bcdf222/accounts-db/src/accounts.rs#L141-L142 */
     204             :   /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L197-L214 */
     205        2517 :   fd_address_lookup_table_state_t table[1];
     206        2517 :   if( FD_UNLIKELY( !fd_bincode_decode_static( address_lookup_table_state, table, alut_data, FD_LOOKUP_TABLE_META_SIZE ) ) ) {
     207           2 :     return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
     208           2 :   }
     209             : 
     210             :   /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L200-L203 */
     211        2515 :   if( FD_UNLIKELY( table->discriminant != fd_address_lookup_table_state_enum_lookup_table ) ) {
     212           1 :     return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
     213           1 :   }
     214             : 
     215             :   /* Again probably an impossible case, but the ALUT data needs to be 32-byte aligned
     216             :       https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L210-L214 */
     217        2514 :   if( FD_UNLIKELY( (alut_data_sz - FD_LOOKUP_TABLE_META_SIZE) & 0x1fUL ) ) {
     218           1 :     return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
     219           1 :   }
     220             : 
     221             :   /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L101-L112 */
     222        2513 :   fd_acct_addr_t const * lookup_addrs     = fd_type_pun_const( alut_data+FD_LOOKUP_TABLE_META_SIZE );
     223        2513 :   ulong                  lookup_addrs_cnt = (alut_data_sz - FD_LOOKUP_TABLE_META_SIZE) >> 5UL; // = (dlen - 56) / 32
     224             : 
     225             :   /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L175-L176 */
     226        2513 :   ulong active_addresses_len;
     227        2513 :   int err = fd_alut_active_addresses_len(
     228        2513 :       &table->inner.lookup_table,
     229        2513 :       interp->slot,
     230        2513 :       interp->hashes,
     231        2513 :       lookup_addrs_cnt,
     232        2513 :       &active_addresses_len
     233        2513 :   );
     234        2513 :   if( FD_UNLIKELY( err ) ) return err;
     235             : 
     236             :   /* https://github.com/anza-xyz/solana-sdk/blob/address-lookup-table-interface%40v3.0.1/address-lookup-table-interface/src/state.rs#L208-L211 */
     237        2509 :   if( FD_UNLIKELY( active_addresses_len>lookup_addrs_cnt ) ) {
     238           1 :     return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
     239           1 :   }
     240             : 
     241             :   /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L169-L182 */
     242        2508 :   uchar const * writable_lut_idxs = interp->txn_payload + addr_lut->writable_off;
     243        4518 :   for( ulong j=0UL; j<addr_lut->writable_cnt; j++ ) {
     244             :     /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
     245        2483 :     if( writable_lut_idxs[j] >= active_addresses_len ) {
     246         473 :       return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
     247         473 :     }
     248        2010 :     interp->out_accts_alt[ interp->rw_indir_cnt++ ] = lookup_addrs[ writable_lut_idxs[ j ] ];
     249        2010 :   }
     250             : 
     251        2035 :   uchar const * readonly_lut_idxs = interp->txn_payload + addr_lut->readonly_off;
     252        2035 :   fd_acct_addr_t * out_accts_ro = interp->out_accts_alt + interp->txn->addr_table_adtl_writable_cnt;
     253        3724 :   for( ulong j=0UL; j<addr_lut->readonly_cnt; j++ ) {
     254             :     /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
     255        1838 :     if( readonly_lut_idxs[j] >= active_addresses_len ) {
     256         149 :       return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
     257         149 :     }
     258        1689 :     out_accts_ro[ interp->ro_indir_cnt++ ] = lookup_addrs[ readonly_lut_idxs[ j ] ];
     259        1689 :   }
     260             : 
     261        1886 :   interp->alut_idx++;
     262        1886 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     263        2035 : }
     264             : 
     265             : FD_PROTOTYPES_END
     266             : 
     267             : 
     268             : #endif /* HEADER_fd_src_flamenco_runtime_fd_alut_h */

Generated by: LCOV version 1.14