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 0 : ulong const max_shred_idx ) { 7 0 : ulong total_shred_sz = sz; 8 : /* Initial bounds check */ 9 0 : if( FD_UNLIKELY( total_shred_sz<fd_ulong_min( FD_SHRED_DATA_HEADER_SZ, FD_SHRED_CODE_HEADER_SZ ) ) ) return NULL; 10 : 11 0 : 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 0 : uchar variant = shred->variant; 16 0 : uchar type = fd_shred_type( variant ); 17 0 : if( FD_UNLIKELY( (type!=FD_SHRED_TYPE_MERKLE_DATA) & 18 0 : (type!=FD_SHRED_TYPE_MERKLE_CODE) & 19 0 : (type!=FD_SHRED_TYPE_MERKLE_DATA_CHAINED) & 20 0 : (type!=FD_SHRED_TYPE_MERKLE_CODE_CHAINED) & 21 0 : (type!=FD_SHRED_TYPE_MERKLE_DATA_CHAINED_RESIGNED) & 22 0 : (type!=FD_SHRED_TYPE_MERKLE_CODE_CHAINED_RESIGNED) & 23 0 : (variant!=0xa5 /*FD_SHRED_TYPE_LEGACY_DATA*/ ) & 24 0 : (variant!=0x5a /*FD_SHRED_TYPE_LEGACY_CODE*/ ) ) ) 25 0 : 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 0 : ulong header_sz = fd_shred_header_sz( variant ); /* between 88 and 89 bytes */ 34 0 : ulong trailer_sz = fd_shred_merkle_sz( shred->variant ) /* between 0 and 300 bytes */ 35 0 : + fd_ulong_if( fd_shred_is_resigned( type ), FD_SHRED_SIGNATURE_SZ, 0UL ) /* 0 or 64 */ 36 0 : + fd_ulong_if( fd_shred_is_chained( type ), FD_SHRED_MERKLE_ROOT_SZ, 0UL ); /* 0 or 32 */ 37 0 : ulong zero_padding_sz; 38 0 : ulong payload_sz; 39 : 40 0 : if( FD_LIKELY( type & FD_SHRED_TYPEMASK_DATA ) ) { 41 0 : if( FD_UNLIKELY( shred->data.size<header_sz ) ) return NULL; 42 0 : payload_sz = (ulong)shred->data.size - header_sz; /* between 0 and USHORT_MAX */ 43 0 : 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 0 : uchar is_legacy_data_shred = type==FD_SHRED_TYPE_LEGACY_DATA; 55 0 : ulong effective_sz = fd_ulong_if( is_legacy_data_shred, sz, FD_SHRED_MIN_SZ ); 56 0 : if( FD_UNLIKELY( effective_sz < header_sz+payload_sz+trailer_sz ) ) return NULL; 57 0 : zero_padding_sz = effective_sz - header_sz - payload_sz - trailer_sz; 58 0 : } 59 0 : else if( FD_LIKELY( type & FD_SHRED_TYPEMASK_CODE ) ) { 60 0 : zero_padding_sz = 0UL; 61 : /* Payload size is not specified directly, but the whole shred must 62 : be FD_SHRED_MAX_SZ. */ 63 0 : if( FD_UNLIKELY( header_sz+zero_padding_sz+trailer_sz > FD_SHRED_MAX_SZ ) ) return NULL; 64 0 : payload_sz = FD_SHRED_MAX_SZ - header_sz - zero_padding_sz - trailer_sz; 65 0 : } 66 0 : else return NULL; 67 : 68 0 : 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 0 : if( FD_LIKELY( type & FD_SHRED_TYPEMASK_DATA ) ) { 73 0 : ulong parent_off = (ulong)shred->data.parent_off; 74 0 : ulong slot = shred->slot; 75 0 : if( FD_UNLIKELY( (shred->data.flags&0xC0)==0x80 ) ) return NULL; 76 0 : if( FD_UNLIKELY( parent_off>slot ) ) return NULL; 77 : 78 : /* Slot 0 is its own parent (parent_off==0); every other slot has 79 : a parent strictly below it, so parent_off must be nonzero. A 80 : parent of slot 0 is valid for any slot: whether it lies below 81 : the root is decided when the shred is ingested, not here. 82 : https://github.com/anza-xyz/agave/blob/v4.4.0-beta.0/ledger/src/shred/traits.rs#L41-L54 */ 83 0 : if( FD_UNLIKELY( (slot!=0UL) & (parent_off==0UL) ) ) return NULL; 84 0 : if( FD_UNLIKELY( shred->idx<shred->fec_set_idx ) ) return NULL; 85 : 86 : /* https://github.com/anza-xyz/agave/blob/v4.0.2/ledger/src/shred/shred_data.rs#L19 */ 87 0 : if( FD_UNLIKELY( shred->idx>=max_shred_idx ) ) return NULL; 88 0 : } else { 89 0 : if( FD_UNLIKELY( shred->code.idx>=shred->code.code_cnt ) ) return NULL; 90 0 : if( FD_UNLIKELY( shred->code.idx> shred->idx ) ) return NULL; 91 0 : if( FD_UNLIKELY( (shred->code.data_cnt==0)|(shred->code.code_cnt==0) ) ) return NULL; 92 0 : if( FD_UNLIKELY( shred->code.code_cnt>256 ) ) return NULL; 93 0 : 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 */ 94 : 95 : /* https://github.com/anza-xyz/agave/blob/v4.0.2/ledger/src/shred/shred_code.rs#L40 */ 96 0 : if( FD_UNLIKELY( shred->idx>=max_shred_idx ) ) return NULL; 97 : /* https://github.com/anza-xyz/agave/blob/v4.0.2/ledger/src/shred/shred_code.rs#L12-L25 98 : Subtractions are safe here due to the checks above. */ 99 0 : if( FD_UNLIKELY( (ulong)shred->fec_set_idx+(ulong)shred->code.data_cnt-1UL>=max_shred_idx ) ) return NULL; 100 0 : if( FD_UNLIKELY( (ulong)(shred->idx-shred->code.idx)+(ulong)shred->code.code_cnt-1UL>=max_shred_idx ) ) return NULL; 101 0 : } 102 : 103 0 : return shred; 104 0 : } 105 : 106 : int 107 0 : fd_shred_merkle_root( fd_shred_t const * shred, void * bmtree_mem, fd_bmtree_node_t * root_out ) { 108 0 : fd_bmtree_commit_t * tree = fd_bmtree_commit_init( bmtree_mem, 109 0 : FD_SHRED_MERKLE_NODE_SZ, 110 0 : FD_BMTREE_LONG_PREFIX_SZ, 111 0 : FD_SHRED_MERKLE_LAYER_CNT ); 112 : 113 0 : uchar shred_type = fd_shred_type( shred->variant ); 114 0 : int is_data_shred = fd_shred_is_data( shred_type ); 115 0 : ulong in_type_idx = fd_ulong_if( is_data_shred, shred->idx - shred->fec_set_idx, shred->code.idx ); 116 0 : ulong shred_idx = fd_ulong_if( is_data_shred, in_type_idx, in_type_idx + shred->code.data_cnt ); 117 : 118 0 : ulong tree_depth = fd_shred_merkle_cnt( shred->variant ); /* In [0, 15] */ 119 0 : if( FD_UNLIKELY( tree_depth!=FD_SHRED_MERKLE_LAYER_CNT-1UL ) ) return 0; 120 0 : ulong reedsol_protected_sz = 1115UL + FD_SHRED_DATA_HEADER_SZ - FD_SHRED_SIGNATURE_SZ - FD_SHRED_MERKLE_NODE_SZ*tree_depth 121 0 : - FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type ) 122 0 : - FD_SHRED_SIGNATURE_SZ *fd_shred_is_resigned( shred_type); /* In [743, 1139] conservatively*/ 123 0 : ulong data_merkle_protected_sz = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type ); 124 0 : 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; 125 0 : ulong merkle_protected_sz = fd_ulong_if( is_data_shred, data_merkle_protected_sz, parity_merkle_protected_sz ); 126 0 : fd_bmtree_node_t leaf; 127 0 : fd_bmtree_hash_leaf( &leaf, (uchar const *)shred + sizeof(fd_ed25519_sig_t), merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ ); 128 : 129 0 : 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 ); 130 0 : }