LCOV - code coverage report
Current view: top level - ballet/txn - fd_txn_parse.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 133 137 97.1 %
Date: 2026-02-13 06:06:24 Functions: 1 1 100.0 %

          Line data    Source code
       1             : /* https://docs.solana.com/developing/programming-model/transactions#anatomy-of-a-transaction */
       2             : 
       3             : #include "fd_txn.h"
       4             : #include "fd_compact_u16.h"
       5             : 
       6             : ulong
       7             : fd_txn_parse_core( uchar const             * payload,
       8             :                    ulong                     payload_sz,
       9             :                    void                    * out_buf,
      10             :                    fd_txn_parse_counters_t * counters_opt,
      11             :                    ulong *                   payload_sz_opt,
      12   121347492 :                    ulong                     instr_max ) {
      13   121347492 :   ulong i = 0UL;
      14             :   /* This code does non-trivial parsing of untrusted user input, which
      15             :      is a potentially dangerous thing.  The main invariants we need to
      16             :      ensure are
      17             :          A)   i<=payload_sz  at all times
      18             :          B)   i< payload_sz  prior to reading
      19             :      As long as these invariants hold, it's safe to read payload[ i ].
      20             :      To ensure this, we force the following discipline for all parsing
      21             :      steps:
      22             :        Step 1. Assert there are enough bytes to read the field
      23             :        Step 2. Read the field
      24             :        Step 3. Advance i
      25             :        Step 4. Validate the field (if there's anything to do)
      26             :      This code is structured highly horizontally to make it very clear
      27             :      that it is correct.
      28             : 
      29             :      The first 3 steps are in three columns.  The variable `i` only
      30             :      appears in very specific locations on the line (try searching for
      31             :      \<i\> in VIM to see this).
      32             : 
      33             :      The CHECK_LEFT( x ) call in the first column and the i+=x in the
      34             :      third column always have the same argument, which ensures invariant
      35             :      A holds.  "Prior to reading" from invariant B corresponds to the
      36             :      middle column, which is the only place `i` is read. Because x is
      37             :      positive, the CHECK_LEFT( x ) in the first column ensures invariant
      38             :      B holds.
      39             : 
      40             :      Unfortunately for variable length integers, we have to combine the
      41             :      first two columns into a call to READ_CHECKED_COMPACT_U16 that also
      42             :      promises not to use any out-of-bounds data.
      43             : 
      44             :      The assignments are done in chunks in as close to the same order as
      45             :      possible as the variables are declared in the struct, making it
      46             :      very clear every variable has been initialized. */
      47             : 
      48             :   /* A temporary for storing the return value of fd_cu16_dec_sz */
      49   121347492 :   ulong bytes_consumed = 0UL;
      50             : 
      51             :   /* Increment counters and return immediately if cond is false. */
      52 15141792189 :   #define CHECK( cond )  do {                                                                                   \
      53 15141792189 :     if( FD_UNLIKELY( !(cond) ) ) {                                                                              \
      54       87588 :       if( FD_LIKELY( counters_opt ) ) {                                                                         \
      55       87585 :         counters_opt->failure_ring[ ( counters_opt->failure_cnt++ )%FD_TXN_PARSE_COUNTERS_RING_SZ ] = __LINE__; \
      56       87585 :       }                                                                                                         \
      57       87588 :       return 0UL;                                                                                               \
      58       87588 :     }                                                                                                           \
      59 15141792189 :   } while( 0 )
      60             :   /* CHECK that it is safe to read at least n more bytes assuming i is
      61             :      the current location. n is untrusted and could trigger overflow, so
      62             :      don't do i+n<=payload_sz */
      63  6741288540 :   #define CHECK_LEFT( n ) CHECK( (n)<=(payload_sz-i) )
      64             :   /* READ_CHECKED_COMPACT_U16 safely reads a compact-u16 from the
      65             :      indicated location in the payload.  It stores the resulting value
      66             :      in the ushort variable called var_name.  It stores the size in
      67             :      out_sz. */
      68   121347492 :   #define READ_CHECKED_COMPACT_U16( out_sz, var_name, where )               \
      69  4069585668 :     do {                                                                    \
      70  4069585668 :       ulong _where = (where);                                               \
      71  4069585668 :       ulong _out_sz = fd_cu16_dec_sz( payload+_where, payload_sz-_where );  \
      72  4069585668 :       CHECK( _out_sz );                                                     \
      73  4069585668 :       (var_name) = fd_cu16_dec_fixed( payload+_where, _out_sz );            \
      74  4069575267 :       (out_sz)   = _out_sz;                                                 \
      75  4069575267 :     } while( 0 )
      76             : 
      77             :   /* Minimal instr has 1B for program id, 1B for an acct_addr list
      78             :      containing no accounts, 1B for length-0 instruction data */
      79   121347492 :   #define MIN_INSTR_SZ (3UL)
      80   121347492 :   CHECK( payload_sz<=FD_TXN_MTU );
      81             : 
      82             :   /* The documentation sometimes calls signature_cnt a compact-u16 and
      83             :      sometimes a u8.  Because of transaction size limits, even allowing
      84             :      for a 3k transaction caps the signatures at 48, so we're
      85             :      comfortably in the range where a compact-u16 and a u8 are
      86             :      represented the same way. */
      87   121347492 :   CHECK_LEFT( 1UL                               );   uchar signature_cnt  = payload[ i ];     i++;
      88             :   /* Must have at least one signer for the fee payer */
      89   121347486 :   CHECK( (1UL<=signature_cnt) & (signature_cnt<=FD_TXN_SIG_MAX) );
      90   121346712 :   CHECK_LEFT( FD_TXN_SIGNATURE_SZ*signature_cnt );   ulong signature_off  =          i  ;     i+=FD_TXN_SIGNATURE_SZ*signature_cnt;
      91             : 
      92             :   /* Not actually parsing anything, just store. */   ulong message_off    =          i  ;
      93   121345065 :   CHECK_LEFT( 1UL                               );   uchar header_b0      = payload[ i ];     i++;
      94             : 
      95   121345059 :   uchar transaction_version;
      96   121345059 :   if( FD_LIKELY( (ulong)header_b0 & 0x80UL ) ) {
      97             :     /* This is a versioned transaction */
      98    60388527 :     transaction_version = header_b0 & 0x7F;
      99    60388527 :     CHECK( transaction_version==FD_TXN_V0 ); /* Only recognized one so far */
     100             : 
     101    60387735 :     CHECK_LEFT( 1UL                             );   CHECK(  signature_cnt==payload[ i ] );   i++;
     102    60956532 :   } else {
     103    60956532 :     transaction_version = FD_TXN_VLEGACY;
     104    60956532 :     CHECK( signature_cnt==header_b0 );
     105    60956532 :   }
     106   121342695 :   CHECK_LEFT( 1UL                               );   uchar ro_signed_cnt  = payload[ i ];     i++;
     107             :   /* Must have at least one writable signer for the fee payer */
     108   121342689 :   CHECK( ro_signed_cnt<signature_cnt );
     109             : 
     110   121341165 :   CHECK_LEFT( 1UL                               );   uchar ro_unsigned_cnt= payload[ i ];     i++;
     111             : 
     112   121341159 :   ushort acct_addr_cnt = (ushort)0;
     113   121341159 :   READ_CHECKED_COMPACT_U16( bytes_consumed,                acct_addr_cnt,            i );     i+=bytes_consumed;
     114   121340385 :   CHECK( (signature_cnt<=acct_addr_cnt) & (acct_addr_cnt<=FD_TXN_ACCT_ADDR_MAX) );
     115   121340370 :   CHECK( (ulong)signature_cnt+(ulong)ro_unsigned_cnt<=(ulong)acct_addr_cnt );
     116             : 
     117   121338873 :   CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt );   ulong acct_addr_off  =          i  ;     i+=FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt;
     118   121335453 :   CHECK_LEFT( FD_TXN_BLOCKHASH_SZ               );   ulong recent_blockhash_off =    i  ;     i+=FD_TXN_BLOCKHASH_SZ;
     119             : 
     120   121335255 :   ushort instr_cnt = (ushort)0;
     121   121335255 :   READ_CHECKED_COMPACT_U16( bytes_consumed,                instr_cnt,                i );     i+=bytes_consumed;
     122             : 
     123             :   /* FIXME: compile-time max after static_instruction_limit. */
     124   121335219 :   CHECK( (ulong)instr_cnt<=instr_max            );
     125             : 
     126   121334064 :   CHECK_LEFT( MIN_INSTR_SZ*instr_cnt            );
     127             :   /* If it has >0 instructions, it must have at least one other account
     128             :      address (the program id) that can't be the fee payer */
     129   121333956 :   CHECK( (ulong)acct_addr_cnt>(!!instr_cnt) );
     130             : 
     131   121333953 :   fd_txn_t * parsed = (fd_txn_t *)out_buf;
     132             : 
     133   121333953 :   if( parsed ) {
     134   121333944 :     parsed->transaction_version           = transaction_version;
     135   121333944 :     parsed->signature_cnt                 = signature_cnt;
     136   121333944 :     parsed->signature_off                 = (ushort)signature_off;
     137   121333944 :     parsed->message_off                   = (ushort)message_off;
     138   121333944 :     parsed->readonly_signed_cnt           = ro_signed_cnt;
     139   121333944 :     parsed->readonly_unsigned_cnt         = ro_unsigned_cnt;
     140   121333944 :     parsed->acct_addr_cnt                 = acct_addr_cnt;
     141   121333944 :     parsed->acct_addr_off                 = (ushort)acct_addr_off;
     142   121333944 :     parsed->recent_blockhash_off          = (ushort)recent_blockhash_off;
     143             :     /* Need to assign addr_table_lookup_cnt,
     144             :        addr_table_adtl_writable_cnt, addr_table_adtl_cnt,
     145             :        _padding_reserved_1 later */
     146   121333944 :     parsed->instr_cnt                     = instr_cnt;
     147   121333944 :   }
     148             : 
     149   121333953 :   uchar max_acct = 0UL;
     150  1508455647 :   for( ulong j=0UL; j<instr_cnt; j++ ) {
     151             : 
     152             :     /* Parsing instruction */
     153  1387142232 :     ushort acct_cnt = (ushort)0;
     154  1387142232 :     ushort data_sz  = (ushort)0;
     155  1387142232 :     CHECK_LEFT( MIN_INSTR_SZ                    );   uchar program_id     = payload[ i ];     i++;
     156  1387141908 :     READ_CHECKED_COMPACT_U16( bytes_consumed,             acct_cnt,                  i );     i+=bytes_consumed;
     157  1387139805 :     CHECK_LEFT( acct_cnt                        );   ulong acct_off       =          i  ;
     158  4548388815 :     for( ulong k=0; k<acct_cnt; k++ ) { max_acct=fd_uchar_max( max_acct,  payload[ k+i ] ); } i+=acct_cnt;
     159  1387135272 :     READ_CHECKED_COMPACT_U16( bytes_consumed,             data_sz,                   i );     i+=bytes_consumed;
     160  1387129494 :     CHECK_LEFT( data_sz                         );   ulong data_off       =          i  ;     i+=data_sz;
     161             : 
     162             :     /* Account 0 is the fee payer and the program can't be the fee
     163             :        payer.  The fee payer account must be owned by the system
     164             :        program, but the program must be an executable account and the
     165             :        system program is not permitted to own any executable account.
     166             :        As of https://github.com/solana-labs/solana/issues/25034, the
     167             :        program ID can't come from a table. */
     168  1387128255 :     CHECK( (0UL < (ulong)program_id) & ((ulong)program_id < (ulong)acct_addr_cnt) );
     169             : 
     170  1387121694 :     if( parsed ){
     171  1387121685 :       parsed->instr[ j ].program_id          = program_id;
     172  1387121685 :       parsed->instr[ j ]._padding_reserved_1 = (uchar)0;
     173  1387121685 :       parsed->instr[ j ].acct_cnt            = acct_cnt;
     174  1387121685 :       parsed->instr[ j ].data_sz             = data_sz;
     175             :       /* By our invariant, i<size when it was copied into acct_off and
     176             :          data_off, and size<=USHORT_MAX from above, so this cast is safe */
     177  1387121685 :       parsed->instr[ j ].acct_off            = (ushort)acct_off;
     178  1387121685 :       parsed->instr[ j ].data_off            = (ushort)data_off;
     179  1387121685 :     }
     180  1387121694 :   }
     181   121313415 :   #undef MIN_INSTR_SIZE
     182             : 
     183   121313415 :   ushort addr_table_cnt               = 0;
     184   121313415 :   ulong  addr_table_adtl_writable_cnt = 0;
     185   121313415 :   ulong  addr_table_adtl_cnt          = 0;
     186             : 
     187             :   /* parsed->instr_cnt set above, so calling get_address_tables is safe */
     188   121313415 :   fd_txn_acct_addr_lut_t * address_tables = (parsed == NULL) ? NULL : fd_txn_get_address_tables( parsed );
     189   121313415 :   if( FD_LIKELY( transaction_version==FD_TXN_V0 ) ) {
     190    60378999 :   #define MIN_ADDR_LUT_SIZE (34UL)
     191    60378999 :     READ_CHECKED_COMPACT_U16( bytes_consumed,             addr_table_cnt,            i );     i+=bytes_consumed;
     192    60378717 :     CHECK( addr_table_cnt <= FD_TXN_ADDR_TABLE_LOOKUP_MAX );
     193    60378255 :     CHECK_LEFT( MIN_ADDR_LUT_SIZE*addr_table_cnt );
     194             : 
     195   556500540 :     for( ulong j=0; j<addr_table_cnt; j++ ) {
     196   496127853 :       CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ           );   ulong addr_off       =          i  ;     i+=FD_TXN_ACCT_ADDR_SZ;
     197             : 
     198   496127580 :       ushort writable_cnt = 0;
     199   496127580 :       ushort readonly_cnt = 0;
     200   496127580 :       READ_CHECKED_COMPACT_U16( bytes_consumed,            writable_cnt,             i );     i+=bytes_consumed;
     201   496126725 :       CHECK_LEFT( writable_cnt                  );   ulong writable_off   =          i  ;     i+=writable_cnt;
     202   496125495 :       READ_CHECKED_COMPACT_U16( bytes_consumed,            readonly_cnt,             i );     i+=bytes_consumed;
     203   496124922 :       CHECK_LEFT( readonly_cnt                  );   ulong readonly_off   =          i  ;     i+=readonly_cnt;
     204             : 
     205   496123194 :       CHECK( writable_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt ); /* implies <256 ... */
     206   496123194 :       CHECK( readonly_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt );
     207   496123194 :       CHECK( (ushort)1   <=writable_cnt+readonly_cnt          ); /* ... so the sum can't overflow */
     208   496123194 :       if( address_tables ) {
     209   496123194 :         address_tables[ j ].addr_off      = (ushort)addr_off;
     210   496123194 :         address_tables[ j ].writable_cnt  = (uchar )writable_cnt;
     211   496123194 :         address_tables[ j ].readonly_cnt  = (uchar )readonly_cnt;
     212   496123194 :         address_tables[ j ].writable_off  = (ushort)writable_off;
     213   496123194 :         address_tables[ j ].readonly_off  = (ushort)readonly_off;
     214   496123194 :       }
     215             : 
     216   496123194 :       addr_table_adtl_writable_cnt += (ulong)writable_cnt;
     217   496123194 :       addr_table_adtl_cnt          += (ulong)writable_cnt + (ulong)readonly_cnt;
     218   496123194 :     }
     219    60377346 :   }
     220   121307103 :   #undef MIN_ADDR_LUT_SIZE
     221             :   /* Check for leftover bytes if out_sz_opt not specified. */
     222   121307103 :   CHECK( (payload_sz_opt!=NULL) | (i==payload_sz) );
     223             : 
     224   121306968 :   CHECK( acct_addr_cnt+addr_table_adtl_cnt<=FD_TXN_ACCT_ADDR_MAX ); /* implies addr_table_adtl_cnt<256 */
     225             : 
     226             :   /* Final validation that all the account address indices are in range */
     227   121306968 :   CHECK( max_acct < acct_addr_cnt + addr_table_adtl_cnt );
     228             : 
     229   121259904 :   if( parsed ) {
     230             :     /* Assign final variables */
     231   121259895 :     parsed->addr_table_lookup_cnt         = (uchar)addr_table_cnt;
     232   121259895 :     parsed->addr_table_adtl_writable_cnt  = (uchar)addr_table_adtl_writable_cnt;
     233   121259895 :     parsed->addr_table_adtl_cnt           = (uchar)addr_table_adtl_cnt;
     234   121259895 :     parsed->_padding_reserved_1           = (uchar)0;
     235   121259895 :   }
     236             : 
     237   121259904 :   if( FD_LIKELY( counters_opt   ) ) counters_opt->success_cnt++;
     238   121259904 :   if( FD_LIKELY( payload_sz_opt ) ) *payload_sz_opt = i;
     239             : 
     240   121259904 :   ulong footprint = fd_txn_footprint( instr_cnt, addr_table_cnt );
     241             :   /* FIXME remove this check when static_instruction_limit is activated on all networks. */
     242   121259904 :   if( FD_UNLIKELY( instr_cnt>FD_TXN_INSTR_MAX && footprint>FD_TXN_MAX_SZ ) ) {
     243           0 :     uchar const * sig = payload+parsed->signature_off;
     244           0 :     FD_LOG_HEXDUMP_WARNING(( "txnsig", sig, FD_TXN_SIGNATURE_SZ ));
     245           0 :     FD_LOG_CRIT(( "instr_cnt %u footprint %lu", instr_cnt, footprint ));
     246           0 :   }
     247   121259904 :   return footprint;
     248             : 
     249   121259904 :   #undef CHECK
     250   121259904 :   #undef CHECK_LEFT
     251   121259904 :   #undef READ_CHECKED_COMPACT_U16
     252   121259904 : }

Generated by: LCOV version 1.14