LCOV - code coverage report
Current view: top level - ballet/shred - fd_shred.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 77 78 98.7 %
Date: 2026-07-05 05:36:26 Functions: 2 2 100.0 %

          Line data    Source code
       1             : #include "fd_shred.h"
       2             : 
       3             : fd_shred_t const *
       4             : fd_shred_parse( uchar const * const buf,
       5             :                 ulong         const sz,
       6       26412 :                 ulong         const max_shred_idx ) {
       7       26412 :   ulong total_shred_sz = sz;
       8             :   /* Initial bounds check */
       9       26412 :   if( FD_UNLIKELY( total_shred_sz<fd_ulong_min( FD_SHRED_DATA_HEADER_SZ, FD_SHRED_CODE_HEADER_SZ ) ) ) return NULL;
      10             : 
      11       26403 :   fd_shred_t const * shred = (fd_shred_t *)buf;
      12             : 
      13             :   /* Validate shred type.
      14             :      Safe to access because `variant` ends at 0x41, which is <= 0x58 */
      15       26403 :   uchar variant = shred->variant;
      16       26403 :   uchar type = fd_shred_type( variant );
      17       26403 :   if( FD_UNLIKELY( (type!=FD_SHRED_TYPE_MERKLE_DATA) &
      18       26403 :                    (type!=FD_SHRED_TYPE_MERKLE_CODE) &
      19       26403 :                    (type!=FD_SHRED_TYPE_MERKLE_DATA_CHAINED) &
      20       26403 :                    (type!=FD_SHRED_TYPE_MERKLE_CODE_CHAINED) &
      21       26403 :                    (type!=FD_SHRED_TYPE_MERKLE_DATA_CHAINED_RESIGNED) &
      22       26403 :                    (type!=FD_SHRED_TYPE_MERKLE_CODE_CHAINED_RESIGNED) &
      23       26403 :                    (variant!=0xa5 /*FD_SHRED_TYPE_LEGACY_DATA*/ ) &
      24       26403 :                    (variant!=0x5a /*FD_SHRED_TYPE_LEGACY_CODE*/ ) ) )
      25         474 :     return NULL;
      26             : 
      27             :   /* There are six sections of a shred that can contribute to the size:
      28             :      header, payload, zero-padding, Merkle root of previous erasure
      29             :      batch, Merkle proof, and retransmitter signature.
      30             :      Some of these may have 0 size in certain cases.  sz is the sum of
      31             :      all 5, while for data shreds, shred->data.size == header+payload.
      32             :      We'll call the last three section the trailer. */
      33       25929 :   ulong header_sz       = fd_shred_header_sz( variant ); /* between 88 and 89 bytes */
      34       25929 :   ulong trailer_sz      = fd_shred_merkle_sz( shred->variant )   /* between 0 and 300 bytes */
      35       25929 :                            + fd_ulong_if( fd_shred_is_resigned( type ), FD_SHRED_SIGNATURE_SZ, 0UL ) /* 0 or 64 */
      36       25929 :                            + fd_ulong_if( fd_shred_is_chained( type ),  FD_SHRED_MERKLE_ROOT_SZ, 0UL ); /* 0 or 32 */
      37       25929 :   ulong zero_padding_sz;
      38       25929 :   ulong payload_sz;
      39             : 
      40       25929 :   if( FD_LIKELY( type & FD_SHRED_TYPEMASK_DATA ) ) {
      41       12675 :     if( FD_UNLIKELY( shred->data.size<header_sz ) ) return NULL;
      42       12675 :     payload_sz = (ulong)shred->data.size - header_sz; /* between 0 and USHORT_MAX */
      43       12675 :     if( FD_UNLIKELY( (type!=FD_SHRED_TYPE_LEGACY_DATA) & (sz<FD_SHRED_MIN_SZ) ) ) return NULL;
      44             : 
      45             :     /* legacy data shreds might be shorter than the normal
      46             :        FD_SHRED_MIN_SZ, but they don't have Merkle proofs, so everything
      47             :        after the payload is zero-padding/ignored.  On the other hand,
      48             :        Merkle data shreds might have some zero-padding, but anything
      49             :        between [FD_SHRED_MIN_SZ, sz) is extra bytes after the shred
      50             :        (which we don't care about the contents of but also tolerate).
      51             :        The Merkle proof is not in bytes [sz-merkle_proof_sz, sz) but in
      52             :        [FD_SHRED_MIN_SZ-merkle_proof_sz, FD_SHRED_MIN_SZ).  From above,
      53             :        we know sz >= FD_SHRED_MIN_SZ in this case. */
      54       12675 :     uchar is_legacy_data_shred = type==FD_SHRED_TYPE_LEGACY_DATA;
      55       12675 :     ulong effective_sz = fd_ulong_if( is_legacy_data_shred, sz, FD_SHRED_MIN_SZ );
      56       12675 :     if( FD_UNLIKELY( effective_sz < header_sz+payload_sz+trailer_sz ) ) return NULL;
      57       12675 :     zero_padding_sz = effective_sz - header_sz - payload_sz - trailer_sz;
      58       12675 :   }
      59       13254 :   else if( FD_LIKELY( type & FD_SHRED_TYPEMASK_CODE ) ) {
      60       13254 :     zero_padding_sz = 0UL;
      61             :     /* Payload size is not specified directly, but the whole shred must
      62             :        be FD_SHRED_MAX_SZ. */
      63       13254 :     if( FD_UNLIKELY( header_sz+zero_padding_sz+trailer_sz > FD_SHRED_MAX_SZ ) ) return NULL;
      64       13254 :     payload_sz      = FD_SHRED_MAX_SZ - header_sz - zero_padding_sz - trailer_sz;
      65       13254 :   }
      66           0 :   else return NULL;
      67             : 
      68       25929 :   if( FD_UNLIKELY( sz < header_sz + payload_sz + zero_padding_sz + trailer_sz ) ) return NULL;
      69             : 
      70             :   /* At this point we know all the fields exist, but we need to sanity
      71             :      check a few fields that would make a shred illegal. */
      72       25929 :   if( FD_LIKELY( type & FD_SHRED_TYPEMASK_DATA ) ) {
      73       12675 :     ulong parent_off = (ulong)shred->data.parent_off;
      74       12675 :     ulong slot       = shred->slot;
      75       12675 :     if( FD_UNLIKELY( (shred->data.flags&0xC0)==0x80                              ) ) return NULL;
      76       12672 :     if( FD_UNLIKELY( parent_off>slot                                             ) ) return NULL;
      77             : 
      78             :     /* There are 3 cases we want to allow:
      79             :         slot==0, parent_off==0
      80             :         slot==1, parent_off==1
      81             :         slot>1,  0<parent_off<slot
      82             :       We've already ensured parent_off<=slot, so the cases we need to reject are:
      83             :         slot==1, parent_off==0
      84             :         slot>1,  parent_off==0
      85             :         slot>1,  parent_off==slot
      86             : 
      87             :       That gives
      88             :       (slot==1 & parent_off==0) | (slot>1 & parent_off==0) | (slot>1 & parent_off==slot)
      89             :       Simplifying a bit,
      90             :       ((slot==1 | slot>1) & parent_off==0) | (slot>1 & parent_off==slot)
      91             :       (slot!=0 & parent_off==0) | (slot>1 & parent_off==slot)
      92             : 
      93             :       https://github.com/anza-xyz/agave/blob/dda8b79162d9aa1191c7813ca7f024ab5a5b0b9f/ledger/src/blockstore.rs#L5035
      94             :     */
      95             : 
      96       12666 :     if( FD_UNLIKELY( ((slot!=0UL) & (parent_off==0UL)) | ((slot>1UL) & (parent_off==slot)) ) ) return NULL;
      97       12660 :     if( FD_UNLIKELY( shred->idx<shred->fec_set_idx                               ) ) return NULL;
      98             : 
      99             :     /* https://github.com/anza-xyz/agave/blob/v4.0.2/ledger/src/shred/shred_data.rs#L19 */
     100       12654 :     if( FD_UNLIKELY( shred->idx>=max_shred_idx                                   ) ) return NULL;
     101       13254 :   } else {
     102       13254 :     if( FD_UNLIKELY( shred->code.idx>=shred->code.code_cnt                       ) ) return NULL;
     103       13248 :     if( FD_UNLIKELY( shred->code.idx> shred->idx                                 ) ) return NULL;
     104       13248 :     if( FD_UNLIKELY( (shred->code.data_cnt==0)|(shred->code.code_cnt==0)         ) ) return NULL;
     105       13245 :     if( FD_UNLIKELY( shred->code.code_cnt>256                                    ) ) return NULL;
     106       13245 :     if( FD_UNLIKELY( (ulong)shred->code.data_cnt+(ulong)shred->code.code_cnt>256 ) ) return NULL; /* I don't see this check in Agave, but it seems necessary */
     107             : 
     108             :     /* https://github.com/anza-xyz/agave/blob/v4.0.2/ledger/src/shred/shred_code.rs#L40 */
     109       13239 :     if( FD_UNLIKELY( shred->idx>=max_shred_idx                                                          ) ) return NULL;
     110             :     /* https://github.com/anza-xyz/agave/blob/v4.0.2/ledger/src/shred/shred_code.rs#L12-L25
     111             :        Subtractions are safe here due to the checks above. */
     112       13233 :     if( FD_UNLIKELY( (ulong)shred->fec_set_idx+(ulong)shred->code.data_cnt-1UL>=max_shred_idx           ) ) return NULL;
     113       13230 :     if( FD_UNLIKELY( (ulong)(shred->idx-shred->code.idx)+(ulong)shred->code.code_cnt-1UL>=max_shred_idx ) ) return NULL;
     114       13230 :   }
     115             : 
     116       25878 :   return shred;
     117       25929 : }
     118             : 
     119             : int
     120           3 : fd_shred_merkle_root( fd_shred_t const * shred, void * bmtree_mem, fd_bmtree_node_t * root_out ) {
     121           3 :   fd_bmtree_commit_t * tree = fd_bmtree_commit_init( bmtree_mem,
     122           3 :                                                      FD_SHRED_MERKLE_NODE_SZ,
     123           3 :                                                      FD_BMTREE_LONG_PREFIX_SZ,
     124           3 :                                                      FD_SHRED_MERKLE_LAYER_CNT );
     125             : 
     126           3 :   uchar shred_type  = fd_shred_type( shred->variant );
     127           3 :   int is_data_shred = fd_shred_is_data( shred_type );
     128           3 :   ulong in_type_idx = fd_ulong_if( is_data_shred, shred->idx - shred->fec_set_idx, shred->code.idx );
     129           3 :   ulong shred_idx   = fd_ulong_if( is_data_shred, in_type_idx, in_type_idx + shred->code.data_cnt  );
     130             : 
     131           3 :   ulong tree_depth           = fd_shred_merkle_cnt( shred->variant ); /* In [0, 15] */
     132           3 :   if( FD_UNLIKELY( tree_depth!=FD_SHRED_MERKLE_LAYER_CNT-1UL ) ) return 0;
     133           3 :   ulong reedsol_protected_sz = 1115UL + FD_SHRED_DATA_HEADER_SZ - FD_SHRED_SIGNATURE_SZ - FD_SHRED_MERKLE_NODE_SZ*tree_depth
     134           3 :                                       - FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type )
     135           3 :                                       - FD_SHRED_SIGNATURE_SZ  *fd_shred_is_resigned( shred_type); /* In [743, 1139] conservatively*/
     136           3 :   ulong data_merkle_protected_sz   = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type );
     137           3 :   ulong parity_merkle_protected_sz = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type )+FD_SHRED_CODE_HEADER_SZ-FD_ED25519_SIG_SZ;
     138           3 :   ulong merkle_protected_sz  = fd_ulong_if( is_data_shred, data_merkle_protected_sz, parity_merkle_protected_sz );
     139           3 :   fd_bmtree_node_t leaf;
     140           3 :   fd_bmtree_hash_leaf( &leaf, (uchar const *)shred + sizeof(fd_ed25519_sig_t), merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ );
     141             : 
     142           3 :   return fd_bmtree_commitp_insert_with_proof( tree, shred_idx, &leaf, (uchar const *)fd_shred_merkle_nodes( shred ), fd_shred_merkle_cnt( shred->variant ), root_out );
     143           3 : }

Generated by: LCOV version 1.14