LCOV - code coverage report
Current view: top level - disco/pack - fd_compute_budget_program.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 87 91 95.6 %
Date: 2026-06-05 08:37:42 Functions: 9 423 2.1 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_disco_pack_fd_compute_budget_program_h
       2             : #define HEADER_fd_src_disco_pack_fd_compute_budget_program_h
       3             : 
       4             : #include "../../ballet/fd_ballet_base.h"
       5             : #include "../../ballet/txn/fd_txn.h"
       6             : 
       7             : /* This header contains utility functions for parsing compute budget
       8             :    program instructions from a transaction. I.e. given a transaction,
       9             :    what is its compute budget limit (in compute units) and what is the
      10             :    additional reward for including it?  Unfortunately, due to the way
      11             :    compute budget program instructions are included in transactions,
      12             :    this is a per-transaction stateful process.
      13             : 
      14             :    This code is designed for high-performance use and so only error
      15             :    checks data coming from the transaction. */
      16             : 
      17             : 
      18             : /* In general, compute budget instructions can occur at most once in a
      19             :    transaction.  If an instruction is duplicated, the transaction is
      20             :    malformed and fails.  These flags are used to keep track of what
      21             :    instructions have been seen so far.  Have I seen a ... */
      22     1435500 : #define FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_CU             ((ushort)0x01) /* ... SetComputeUnitLimit ... */
      23     1435497 : #define FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_FEE            ((ushort)0x02) /* ... SetComputeUnitPrice ... */
      24          36 : #define FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_HEAP           ((ushort)0x04) /* ... RequestHeapFrame ... */
      25     1435461 : #define FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_LOADED_DATA_SZ ((ushort)0x08) /* ... LoadedAccountDataSize ... */
      26             :                                                                          /* ... so far? */
      27             : 
      28             : 
      29             : /* NOTE: THE FOLLOWING CONSTANTS ARE CONSENSUS CRITICAL AND CANNOT BE
      30             :    CHANGED WITHOUT COORDINATING WITH ANZA. */
      31             : 
      32             : /* base58 decode of ComputeBudget111111111111111111111111111111 */
      33             : static const uchar FD_COMPUTE_BUDGET_PROGRAM_ID[FD_TXN_ACCT_ADDR_SZ] = {
      34             :   0x03,0x06,0x46,0x6f,0xe5,0x21,0x17,0x32,0xff,0xec,0xad,0xba,0x72,0xc3,0x9b,0xe7,
      35             :   0xbc,0x8c,0xe5,0xbb,0xc5,0xf7,0x12,0x6b,0x2c,0x43,0x9b,0x3a,0x40,0x00,0x00,0x00
      36             : };
      37             : 
      38             : /* Heap frames must be between 32 KiB and 256 KiB inclusive, and any
      39             :    requests for larger heap frames must be a multiple of 1 KiB or the
      40             :    transaction is malformed. */
      41             : #define FD_COMPUTE_BUDGET_MIN_HEAP_FRAME_BYTES           (  32UL*1024UL)
      42             : #define FD_COMPUTE_BUDGET_MAX_HEAP_FRAME_BYTES           ( 256UL*1024UL)
      43             : #define FD_COMPUTE_BUDGET_HEAP_FRAME_GRANULARITY         (       1024UL)
      44             : /* SetComputeUnitPrice specifies the price in "micro-lamports," which is
      45             :    10^(-6) lamports, so 10^(-15) SOL. */
      46    95288193 : #define FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT     (    1000000UL)
      47             : 
      48    12177123 : #define FD_COMPUTE_BUDGET_MAX_BUILTIN_CU_LIMIT           (       3000UL)
      49    12177123 : #define FD_COMPUTE_BUDGET_DEFAULT_INSTR_CU_LIMIT         (     200000UL)
      50    15048099 : #define FD_COMPUTE_BUDGET_MAX_CU_LIMIT                   (    1400000UL)
      51    13612599 : #define FD_COMPUTE_BUDGET_HEAP_COST                      (          8UL)
      52    27225198 : #define FD_COMPUTE_BUDGET_ACCOUNT_DATA_COST_PAGE_SIZE    (32UL * 1024UL)
      53             : 
      54             : /* Max loaded data size is 64 MiB */
      55    13612614 : #define FD_COMPUTE_BUDGET_MAX_LOADED_DATA_SZ    (64UL*1024UL*1024UL)
      56             : 
      57             : /* ---- End consensus-critical constants */
      58             : 
      59             : 
      60             : struct fd_compute_budget_program_private_state {
      61             :   /* flags: Which instructions have been parsed so far in this transaction? See
      62             :      above for their meaning. */
      63             :   ushort  flags;
      64             :   /* compute_budge_instr_cnt: How many compute budget instructions have been
      65             :      parsed so far? compute_budget_instr_cnt in [0, 4]. */
      66             :   ushort  compute_budget_instr_cnt;
      67             :   /* compute_units: if SET_CU is in flags, this stores the total requested
      68             :      compute units for the whole transaction. Otherwise 0. Realistically should
      69             :      be less than 12M, but there's nothing enforcing that at this stage. */
      70             :   uint    compute_units;
      71             :   /* loaded_acct_data_sz: if SET_LOADED_DATA_SZ is in flags, this stores
      72             :      the max total data (in bytes) that the transaction will load from
      73             :      the accounts it references.  Otherwise, 0. */
      74             :   uint    loaded_acct_data_sz;
      75             :   /* heap_size: if SET_HEAP is in flags, this stores the size in bytes of the
      76             :      BPF heap used for executing this transaction. Otherwise, 0. Must be a
      77             :      multiple of 1024. */
      78             :   uint    heap_size;
      79             :   /* micro_lamports_per_cu: if SET_FEE is in flags, this stores the
      80             :      requested prioritization fee in micro-lamports per compute unit.
      81             :      Otherwise, 0. */
      82             :   ulong   micro_lamports_per_cu;
      83             : };
      84             : typedef struct fd_compute_budget_program_private_state fd_compute_budget_program_state_t;
      85             : 
      86             : /* fd_compute_budge_program_init: initializes an
      87             :    fd_compute_budget_program_state_t to prepare it for parsing a transaction.
      88             :    Also equivalent to just initializing the state on the stack with = {0}. */
      89    13612677 : static inline void fd_compute_budget_program_init( fd_compute_budget_program_state_t * state ) {
      90    13612677 :   *state = (fd_compute_budget_program_state_t) {0};
      91    13612677 : }
      92             : /* fd_compute_budget_program_parse: Parses a single ComputeBudgetProgram
      93             :    instruction.  Updates the state stored in state.  Returns 0 if the
      94             :    instruction was invalid, which means the transaction should fail.
      95             :    instr_data points to the first byte of the instruction data from the
      96             :    transaction.  data_sz specifies the length of the instruction data, so
      97             :    instr_data[ i ] for i in [0, data_sz) gives the instruction data. */
      98             : static inline int
      99             : fd_compute_budget_program_parse( uchar const * instr_data,
     100             :                                  ulong         data_sz,
     101     4306548 :                                  fd_compute_budget_program_state_t * state ) {
     102     4306548 :   if( FD_UNLIKELY( data_sz<5 ) ) return 0;
     103     4306548 :   switch( *instr_data ) {
     104          18 :     case 0:
     105             :       /* Formerly RequestUnitsDeprecated, but invalid since
     106             :          EfhYd3SafzGT472tYQDUc4dPd2xdEfKs5fwkowUgVt4W. */
     107          18 :       return 0;
     108          60 :     case 1:
     109             :       /* Parse a RequestHeapFrame instruction */
     110          60 :       if( FD_UNLIKELY( data_sz<5 ) ) return 0;
     111          60 :       if( FD_UNLIKELY( (state->flags & FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_HEAP)!=0 ) ) return 0;
     112          60 :       state->heap_size = FD_LOAD( uint, instr_data+1 );
     113          60 :       if( FD_UNLIKELY( state->heap_size%FD_COMPUTE_BUDGET_HEAP_FRAME_GRANULARITY ) ) return 0;
     114          51 :       if( FD_UNLIKELY( state->heap_size<FD_COMPUTE_BUDGET_MIN_HEAP_FRAME_BYTES || state->heap_size>FD_COMPUTE_BUDGET_MAX_HEAP_FRAME_BYTES ) ) return 0;
     115          36 :       state->flags |= FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_HEAP;
     116          36 :       state->compute_budget_instr_cnt++;
     117          36 :       return 1;
     118     1435503 :     case 2:
     119             :       /* Parse a SetComputeUnitLimit instruction */
     120     1435503 :       if( FD_UNLIKELY( data_sz<5 ) ) return 0;
     121     1435503 :       if( FD_UNLIKELY( (state->flags & FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_CU)!=0 ) ) return 0;
     122     1435500 :       state->compute_units = FD_LOAD( uint, instr_data+1 );
     123     1435500 :       state->compute_units = fd_uint_min( state->compute_units, FD_COMPUTE_BUDGET_MAX_CU_LIMIT );
     124     1435500 :       state->flags |= FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_CU;
     125     1435500 :       state->compute_budget_instr_cnt++;
     126     1435500 :       return 1;
     127     1435500 :     case 3:
     128             :       /* Parse a SetComputeUnitPrice instruction */
     129     1435500 :       if( FD_UNLIKELY( data_sz<9 ) ) return 0;
     130     1435500 :       if( FD_UNLIKELY( (state->flags & FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_FEE)!=0 ) ) return 0;
     131     1435497 :       state->micro_lamports_per_cu = FD_LOAD( ulong, instr_data+1 );
     132     1435497 :       state->flags |= FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_FEE;
     133     1435497 :       state->compute_budget_instr_cnt++;
     134     1435497 :       return 1;
     135     1435464 :     case 4:
     136             :       /* Parse a SetLoadedAccountsDataSizeLimit instruction */
     137     1435464 :       if( FD_UNLIKELY( data_sz<5 ) ) return 0;
     138     1435464 :       if( FD_UNLIKELY( (state->flags & FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_LOADED_DATA_SZ)!=0 ) ) return 0;
     139     1435461 :       state->loaded_acct_data_sz = FD_LOAD( uint, instr_data+1 );
     140     1435461 :       if( FD_UNLIKELY( state->loaded_acct_data_sz==0U ) ) return 0;
     141     1435461 :       state->loaded_acct_data_sz = fd_uint_min( state->loaded_acct_data_sz, FD_COMPUTE_BUDGET_MAX_LOADED_DATA_SZ );
     142     1435461 :       state->flags |= FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_LOADED_DATA_SZ;
     143     1435461 :       state->compute_budget_instr_cnt++;
     144     1435461 :       return 1;
     145           3 :     default:
     146           3 :       return 0;
     147     4306548 :   }
     148     4306548 : }
     149             : 
     150             : /* fd_compute_budget_program_finalize: digests the state that resulted
     151             :    from processing all of the ComputeBudgetProgram instructions in a
     152             :    transaction to compute the total priority rewards for the
     153             :    transaction.  state must point to a previously initialized
     154             :    fd_compute_budget_program_state_t.  instr_cnt is the total number of
     155             :    instructions in the transaction, including ComputeBudgetProgram
     156             :    instructions.  builtin_instr_cnt is the total number of builtin
     157             :    instructions in the transaction.  The set of builtin instructions is
     158             :    dependent on both the agave codebase and active feature flags.  This
     159             :    codebase uses a hardcoded list of instructions that should be updated
     160             :    to exclude instructions as they are migrated to non-builtin status.
     161             :    out_rewards and out_compute must be non-null.  The total priority
     162             :    rewards for the transaction (i.e. not counting the per-signature fee)
     163             :    is stored in out_rewards.  The maximum number of compute units this
     164             :    transaction can consume is stored in out_compute.  If the transaction
     165             :    execution has not completed by this limit, it is terminated and
     166             :    considered failed. */
     167             : static inline void
     168             : fd_compute_budget_program_finalize( fd_compute_budget_program_state_t const * state,
     169             :                                     ulong                                     instr_cnt,
     170             :                                     ulong                                     builtin_instr_cnt,
     171             :                                     ulong *                                   out_rewards,
     172             :                                     uint *                                    out_compute,
     173    13612599 :                                     ulong *                                   out_loaded_account_data_cost ) {
     174    13612599 :   ulong cu_limit = 0UL;
     175    13612599 :   if( FD_LIKELY( (state->flags & FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_CU)==0U ) ) {
     176             :     /* Use default compute limit */
     177    12177123 :     cu_limit = (instr_cnt - builtin_instr_cnt) * FD_COMPUTE_BUDGET_DEFAULT_INSTR_CU_LIMIT + builtin_instr_cnt * FD_COMPUTE_BUDGET_MAX_BUILTIN_CU_LIMIT;
     178    12177123 :   } else cu_limit = state->compute_units;
     179             : 
     180    13612599 :   cu_limit = fd_ulong_min( cu_limit, FD_COMPUTE_BUDGET_MAX_CU_LIMIT );
     181             : 
     182    13612599 :   *out_compute = (uint)cu_limit;
     183             : 
     184    13612599 :   ulong loaded_accounts_data_size = 0UL;
     185    13612599 :   if( FD_LIKELY( (state->flags & FD_COMPUTE_BUDGET_PROGRAM_FLAG_SET_LOADED_DATA_SZ)==0U ) ) {
     186             :     /* Use default loaded account data size */
     187    12177153 :     loaded_accounts_data_size = FD_COMPUTE_BUDGET_MAX_LOADED_DATA_SZ;
     188    12177153 :   } else loaded_accounts_data_size = state->loaded_acct_data_sz;
     189             : 
     190             :   /* https://github.com/firedancer-io/agave/blob/927c6d30ed5e1baea30c06ebf2aa0c9bae0e1dd1/sdk/fee-structure/src/lib.rs#L122-L129
     191             : 
     192             :      loaded_accounts_data_size <= FD_COMPUTE_BUDGET_MAX_LOADED_DATA_SZ
     193             :      means no overflow */
     194    13612599 :   ulong loaded_accounts_data_cost = FD_COMPUTE_BUDGET_HEAP_COST * ( (
     195    13612599 :    loaded_accounts_data_size + ( FD_COMPUTE_BUDGET_ACCOUNT_DATA_COST_PAGE_SIZE - 1UL )
     196    13612599 :   ) / FD_COMPUTE_BUDGET_ACCOUNT_DATA_COST_PAGE_SIZE );
     197    13612599 :   *out_loaded_account_data_cost = loaded_accounts_data_cost;
     198             : 
     199    13612599 :   ulong total_fee = 0UL;
     200             : 
     201             :   /* We need to compute max(ceil((cu_limit * micro_lamports_per_cu)/10^6),
     202             :      ULONG_MAX).  Unfortunately, the product can overflow.  Solana solves
     203             :      this by doing the arithmetic with ultra-wide integers, but that puts a
     204             :      128-bit division on the critical path.  Gross.  It's frustrating because
     205             :      the overflow case likely results in a transaction that's so expensive
     206             :      nobody can afford it anyways, but we should not break compatibility with
     207             :      Solana over this.  Instead we'll do the arithmetic carefully:
     208             :      Let cu_limit = c_h*10^6 + c_l, where 0 <= c_l < 10^6.
     209             :      Similarly, let micro_lamports_per_cu = p_h*10^6 + p_l, where
     210             :      0 <= p_l < 10^6.  Since cu_limit < 2^32, c_h < 2^13;
     211             :      micro_lamports_per_cu < 2^64, so p_h<2^45.
     212             : 
     213             :      ceil( (cu_limit * micro_lamports_per_cu)/10^6)
     214             :      = ceil( ((c_h*10^6+c_l)*(p_h*10^6+p_l))/10^6 )
     215             :      = c_h*p_h*10^6 + c_h*p_l + c_l*p_h + ceil( (c_l*p_l)/10^6 )
     216             :      c_h*p_h < 2^58, so we can compute it with normal multiplication.
     217             :      If c_h*p_h > floor(ULONG_MAX/10^6), then we know c_h*p_h*10^6 will hit
     218             :      the saturation point.  The "cross" terms are less than 2^64 by
     219             :      construction (since we divided by 10^6 and then multiply by something
     220             :      strictly less than 10^6).  c_l*p_l < 10^12 < 2^40, so that's safe as
     221             :      well.
     222             :      In fact, the sum of the right three terms is no larger than:
     223             :      floor((2^32-1)/10^6)*(10^6-1) + (10^6-1)*floor((2^64-1)/10^6) + 10^6-1
     224             :      == 0xffffef3a08574e4c < 2^64, so we can do the additions without
     225             :      worrying about overflow.
     226             :      Of course, we still need to check the final addition of the first term
     227             :      with the remaining terms.  As a bonus, all of the divisions can now be
     228             :      done via "magic multiplication."
     229             : 
     230             :      Note that this computation was done before I was aware of the
     231             :      1.4M CU limit.  Taking that limit into account could make the
     232             :      code a little cleaner, but we'll just keep the version that
     233             :      supports CU limits up to UINT_MAX, since I'm sure the limit will
     234             :      go up someday. */
     235    13612599 :   do {
     236    13612599 :     ulong c_h  =                     cu_limit / FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT;
     237    13612599 :     ulong c_l  =                     cu_limit % FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT;
     238    13612599 :     ulong p_h  = state->micro_lamports_per_cu / FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT;
     239    13612599 :     ulong p_l  = state->micro_lamports_per_cu % FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT;
     240             : 
     241    13612599 :     ulong hh = c_h * p_h;
     242    13612599 :     if( FD_UNLIKELY( hh>(ULONG_MAX/FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT) ) ) {
     243           0 :       total_fee = ULONG_MAX;
     244           0 :       break;
     245           0 :     }
     246    13612599 :     hh *= FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT;
     247             : 
     248    13612599 :     ulong hl = c_h*p_l + c_l*p_h;
     249    13612599 :     ulong ll = (c_l*p_l + FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT - 1UL)/FD_COMPUTE_BUDGET_MICRO_LAMPORTS_PER_LAMPORT;
     250    13612599 :     ulong right_three_terms = hl + ll;
     251             : 
     252    13612599 :     total_fee = hh + right_three_terms;
     253    13612599 :     if( FD_UNLIKELY( total_fee<hh ) ) total_fee = ULONG_MAX;
     254    13612599 :   } while( 0 );
     255           0 :   *out_rewards = total_fee;
     256    13612599 : }
     257             : 
     258             : #endif /* HEADER_fd_src_disco_pack_fd_compute_budget_program_h */

Generated by: LCOV version 1.14