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 */