Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_runtime_fd_cost_tracker_h 2 : #define HEADER_fd_src_flamenco_runtime_fd_cost_tracker_h 3 : 4 : /* fd_cost_tracker_t is a block-level tracker for various limits 5 : including CU consumption, writable account usage, and account data 6 : size. A cost is calculated per-transaction and is accumulated to the 7 : block. If a block's limits are exceeded, then the block is marked as 8 : dead. */ 9 : 10 : #include "fd_executor.h" 11 : #include "fd_runtime_err.h" 12 : #include "../../disco/pack/fd_pack.h" /* TODO: Layering violation */ 13 : #include "../../disco/pack/fd_pack_cost.h" 14 : 15 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L62-L79 */ 16 : 17 198 : #define FD_WRITE_LOCK_UNITS ( 300U) /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/block_cost_limits.rs#L20 */ 18 : #define FD_MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA (100000000U) /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/block_cost_limits.rs#L42 */ 19 3528 : #define FD_MAX_WRITABLE_ACCOUNT_UNITS ( 12000000U) /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/block_cost_limits.rs#L34 */ 20 3516 : #define FD_MAX_BLOCK_UNITS_SIMD_0207 ( 50000000U) /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/block_cost_limits.rs#L50-L56 */ 21 0 : #define FD_MAX_BLOCK_UNITS_SIMD_0256 ( 60000000U) /* https://github.com/anza-xyz/agave/blob/v2.3.0/cost-model/src/block_cost_limits.rs#L50-L56 */ 22 12 : #define FD_MAX_BLOCK_UNITS_SIMD_0286 (100000000U) /* https://github.com/anza-xyz/agave/blob/v3.0.0/cost-model/src/block_cost_limits.rs#L30 */ 23 3528 : #define FD_MAX_VOTE_UNITS ( 36000000U) /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/block_cost_limits.rs#L38 */ 24 0 : #define FD_SIMPLE_VOTE_USAGE_COST ( 3428U) /* https://github.com/anza-xyz/agave/blob/v3.1.8/cost-model/src/transaction_cost.rs#L21 */ 25 : 26 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L18-L33 */ 27 114 : #define FD_COST_TRACKER_SUCCESS (0) 28 0 : #define FD_COST_TRACKER_ERROR_WOULD_EXCEED_BLOCK_MAX_LIMIT (1) 29 0 : #define FD_COST_TRACKER_ERROR_WOULD_EXCEED_VOTE_MAX_LIMIT (2) 30 0 : #define FD_COST_TRACKER_ERROR_WOULD_EXCEED_ACCOUNT_MAX_LIMIT (3) 31 0 : #define FD_COST_TRACKER_ERROR_WOULD_EXCEED_ACCOUNT_DATA_BLOCK_LIMIT (4) 32 0 : #define FD_COST_TRACKER_ERROR_WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT (5) 33 : 34 : /* A reasonably tight bound of the number of writable accounts per slot 35 : can be derived from worst-case CU limits. For the cost tracker, 36 : there are two main codepaths: 37 : - Simple vote transactions: legacy transactions that invoke the vote 38 : program. These can have unused writable accounts. 39 : - Everything else: these transactions are charged based on CUs per 40 : signature and write lock. 41 : 42 : For the simple vote transactions, the worst-case transaction has 35 43 : writable accounts. These transactions can not use ALTs and are 44 : subject to fitting within the transaction size limit (1232B). 45 : 46 : 36000000 vote CUs per slot 47 : 3428 CUs per simple vote transaction 48 : 35 writable accounts per simple vote transaction 49 : Therefore, the number of simple vote transactions per slot is: 50 : (36000000 / 3428) = 10501 vote txns per slot 51 : Therefore, the number of writable accounts per slot is: 52 : (10501 * 35) = 367535 writable accounts per slot 53 : 54 : Now consider the general case. This means we should try to pack as 55 : many writable accounts as possible into each transaction. Each 56 : transaction requires at least one signature. We will assume that all 57 : of these accounts have no account data. 58 : 59 : 64 - Max number of accounts per transaction. In this case we will 60 : assume that all of these accounts are writable and have no data. 61 : 100000000 - CUs per slot 62 : 720 - Cost of a signature 63 : 300 - Cost of a writable account write lock 64 : 65 : We can have (100000000 / (720 + 64 * 300)) = 5020 transactions per 66 : slot with maximum writable account utilization. 67 : 68 : So, 5020 transactions per slot * 64 accounts per transaction = 69 : 321280 writable accounts per slot. 70 : 71 : We should use the bound derived from simple vote transactions. 72 : 73 : TODO: After remove_simple_vote_from_cost_model is activated, the 74 : smaller bound can be used. */ 75 : 76 : #define FD_RUNTIME_MAX_VOTE_TXNS_IN_SLOT (FD_MAX_VOTE_UNITS / FD_SIMPLE_VOTE_USAGE_COST) 77 : #define FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_VOTE_TXNS (35UL) /* see FD_TXN_ACCT_ADDR_MAX */ 78 : #define FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_SLOT (FD_RUNTIME_MAX_VOTE_TXNS_IN_SLOT * FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_VOTE_TXNS) 79 : FD_STATIC_ASSERT( FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_SLOT==367535UL, "Incorrect FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_SLOT" ); 80 : 81 : /* TODO: Extremely gross. Used because these are in a pool which needs 82 : to be compile time sized T. */ 83 : 84 : /* The average mainnet block uses around 4200 distinct writable 85 : accounts. We size the cost tracker's account map at 8192 chains 86 : to give ~2x headroom over the observed average load. */ 87 801 : #define FD_COST_TRACKER_CHAIN_CNT_EST (8192UL) 88 : #define FD_COST_TRACKER_FOOTPRINT \ 89 : ( FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( \ 90 : FD_LAYOUT_INIT, \ 91 : 128UL /* alignof(fd_cost_tracker_t) */, 128UL /* sizeof(fd_cost_tracker_t) */ ), \ 92 : 128UL /* alignof(cost_tracker_out_t )*/, 128UL /* sizeof(cost_tracker_out_t ) */ ), \ 93 : 8UL /* alignof(account_cost_map_t) */, FD_COST_TRACKER_CHAIN_CNT_EST*4UL /*sizeof(uint)*/ +24UL /* sizeof(account_cost_map_t) */ ), \ 94 : 4UL /* alignof(account_cost_t) */, FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_SLOT*40UL /*sizeof(account_cost_t)*/ ), \ 95 : 128UL ) ) \ 96 : 97 792 : #define FD_COST_TRACKER_MAGIC (0xF17EDA2CE7C05170UL) /* FIREDANCER COST V0 */ 98 : 99 2424 : #define FD_COST_TRACKER_ALIGN (128UL) 100 : 101 : struct __attribute__((aligned(FD_COST_TRACKER_ALIGN))) fd_cost_tracker { 102 : ulong block_cost; 103 : ulong vote_cost; 104 : ulong allocated_accounts_data_size; 105 : 106 : ulong block_cost_limit; 107 : ulong vote_cost_limit; 108 : ulong account_cost_limit; 109 : 110 : int larger_max_cost_per_block; 111 : int remove_simple_vote_from_cost_model; 112 : }; 113 : 114 : typedef struct fd_cost_tracker fd_cost_tracker_t; 115 : 116 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/transaction_cost.rs#L153-L161 */ 117 : 118 : struct fd_usage_cost_details { 119 : uint signature_cost; 120 : uint write_lock_cost; 121 : uint data_bytes_cost; 122 : uint programs_execution_cost; 123 : uint loaded_accounts_data_size_cost; 124 : ulong allocated_accounts_data_size; 125 : }; 126 : typedef struct fd_usage_cost_details fd_usage_cost_details_t; 127 : 128 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/transaction_cost.rs#L20-L23 */ 129 : 130 : struct fd_transaction_cost { 131 : uint type; /* FD_TXN_COST_TYPE_* */ 132 : union { 133 : fd_usage_cost_details_t transaction; 134 : }; 135 : }; 136 : 137 : typedef struct fd_transaction_cost fd_transaction_cost_t; 138 : 139 0 : #define FD_TXN_COST_TYPE_SIMPLE_VOTE 0U 140 426 : #define FD_TXN_COST_TYPE_TRANSACTION 1U 141 : 142 : FD_PROTOTYPES_BEGIN 143 : 144 : static inline int 145 0 : fd_cost_tracker_err_to_runtime_err( int err ) { 146 0 : switch( err ) { 147 0 : case FD_COST_TRACKER_SUCCESS: 148 0 : return FD_RUNTIME_EXECUTE_SUCCESS; 149 0 : case FD_COST_TRACKER_ERROR_WOULD_EXCEED_BLOCK_MAX_LIMIT: 150 0 : return FD_RUNTIME_TXN_ERR_WOULD_EXCEED_MAX_BLOCK_COST_LIMIT; 151 0 : case FD_COST_TRACKER_ERROR_WOULD_EXCEED_VOTE_MAX_LIMIT: 152 0 : return FD_RUNTIME_TXN_ERR_WOULD_EXCEED_MAX_VOTE_COST_LIMIT; 153 0 : case FD_COST_TRACKER_ERROR_WOULD_EXCEED_ACCOUNT_MAX_LIMIT: 154 0 : return FD_RUNTIME_TXN_ERR_WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT; 155 0 : case FD_COST_TRACKER_ERROR_WOULD_EXCEED_ACCOUNT_DATA_BLOCK_LIMIT: 156 0 : return FD_RUNTIME_TXN_ERR_WOULD_EXCEED_ACCOUNT_DATA_BLOCK_LIMIT; 157 0 : case FD_COST_TRACKER_ERROR_WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT: 158 0 : return FD_RUNTIME_TXN_ERR_WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT; 159 0 : default: 160 0 : FD_LOG_CRIT(( "unexpected cost tracker error %d", err )); 161 0 : } 162 0 : } 163 : 164 : FD_FN_CONST ulong 165 : fd_cost_tracker_align( void ); 166 : 167 : FD_FN_CONST ulong 168 : fd_cost_tracker_footprint( void ); 169 : 170 : void * 171 : fd_cost_tracker_new( void * shmem, 172 : int larger_max_cost_per_block, 173 : ulong seed ); 174 : 175 : fd_cost_tracker_t * 176 : fd_cost_tracker_join( void * shct ); 177 : 178 : void 179 : fd_cost_tracker_init( fd_cost_tracker_t * cost_tracker, 180 : fd_features_t const * features, 181 : ulong slot ); 182 : 183 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L323-L328 */ 184 : FD_FN_PURE uint 185 : fd_cost_tracker_calculate_loaded_accounts_data_size_cost( fd_txn_out_t const * txn_out ); 186 : 187 : /* fd_cost_tracker_calculate_cost_and_add takes a transaction, 188 : calculates the cost of the transaction in terms of various block 189 : level limits and adds it to the cost tracker. If the incremental 190 : transaction fits in the block, then the cost tracking is updated and 191 : FD_COST_TRACKER_SUCCESS is returned. If the transaction does not 192 : fit then FD_COST_TRACKER_ERROR_{*} is returned depending on what 193 : limit is violated. 194 : 195 : This function assumes that the caller is responsible for managing 196 : concurrent callers. 197 : 198 : This function represents the Agave client function: 199 : `CostModel::calculate_cost_for_executed_transaction()` 200 : 201 : https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_model.rs#L69-L95 202 : */ 203 : 204 : void 205 : fd_cost_tracker_calculate_cost( fd_bank_t * bank, 206 : fd_txn_in_t const * txn_in, 207 : fd_txn_out_t * txn_out ); 208 : 209 : /* fd_cost_tracker_try_add_cost takes the cost as calculated by 210 : fd_cost_tracker_calculate_cost and tries to accumulate it into the 211 : cost tracker. If the cost fits, the cost is added and 212 : FD_COST_TRACKER_SUCCESS is returned. If the cost does not fit, the 213 : cost will not be updated and FD_COST_TRACKER_ERROR_* is returned 214 : depending on what limit is violated. 215 : 216 : This function is the Agave client function: 'CostTracker::try_add()' 217 : https://github.com/anza-xyz/agave/blob/v2.2.0/cost-model/src/cost_tracker.rs#L163-L173 */ 218 : 219 : int 220 : fd_cost_tracker_try_add_cost( fd_cost_tracker_t * cost_tracker, 221 : fd_txn_out_t * txn_out ); 222 : 223 : FD_PROTOTYPES_END 224 : 225 : #endif /* HEADER_fd_src_flamenco_runtime_fd_cost_tracker_h */