Line data Source code
1 : #ifndef HEADER_fd_src_choreo_eqvoc_fd_eqvoc_h 2 : #define HEADER_fd_src_choreo_eqvoc_fd_eqvoc_h 3 : 4 : #include "../fd_choreo_base.h" 5 : #include "../../ballet/shred/fd_shred.h" 6 : #include "../../flamenco/leaders/fd_leaders.h" 7 : #include "../../flamenco/gossip/fd_gossip_message.h" 8 : 9 : /* fd_eqvoc presents an API for detecting and sending & receiving proofs 10 : of equivocation. Agave calls "equivocation" duplicates, including in 11 : their type names, so these terms will be used interchangeably for the 12 : sake of conformity. 13 : 14 : Equivocation is when a leader produces two or more blocks for the 15 : same slot. Proving equivocation does not require the complete blocks 16 : however; in fact, only two shreds are required. The idea is these 17 : shreds conflict in a way that implies equivocating blocks for a slot. 18 : See `verify_proof` in fd_eqvoc.c for details. */ 19 : 20 0 : #define FD_EQVOC_SUCCESS (0) /* shreds do not equivocate */ 21 : 22 : /* proof successfully reassembled from chunked and verified */ 23 : 24 0 : #define FD_EQVOC_SUCCESS_MERKLE (1) 25 0 : #define FD_EQVOC_SUCCESS_META (2) 26 0 : #define FD_EQVOC_SUCCESS_LAST (3) 27 0 : #define FD_EQVOC_SUCCESS_OVERLAP (4) 28 0 : #define FD_EQVOC_SUCCESS_CHAINED (5) 29 : 30 : /* proof successfully reassembled from chunked but not verified */ 31 : 32 0 : #define FD_EQVOC_ERR_SERDE (-1) /* invalid serialization */ 33 0 : #define FD_EQVOC_ERR_SLOT (-2) /* shreds were for different slots */ 34 0 : #define FD_EQVOC_ERR_VERSION (-3) /* either shred had wrong shred version */ 35 0 : #define FD_EQVOC_ERR_TYPE (-4) /* wrong shred type (must be chained merkle) */ 36 0 : #define FD_EQVOC_ERR_MERKLE (-5) /* failed to derive merkle root */ 37 0 : #define FD_EQVOC_ERR_SIG (-6) /* failed to sigverify */ 38 : 39 : /* chunk was invalid */ 40 : 41 0 : #define FD_EQVOC_ERR_CHUNK_CNT (-7) /* num_chunks != FD_EQVOC_CHUNK_CNT */ 42 0 : #define FD_EQVOC_ERR_CHUNK_IDX (-8) /* chunk_index >= FD_EQVOC_CHUNK_CNT */ 43 0 : #define FD_EQVOC_ERR_CHUNK_LEN (-9) /* chunk_len does not match expected length for chunk_index */ 44 : 45 : /* chunk was ignored */ 46 : 47 0 : #define FD_EQVOC_ERR_IGNORED_FROM (-10) /* unrecognized from address */ 48 0 : #define FD_EQVOC_ERR_IGNORED_SLOT (-11) /* slot older than root or unable to derive leader schedule */ 49 : 50 : /* FD_EQVOC_CHUNK_CNT: the count of chunks is hardcoded because Agave 51 : discards any chunks where count != 3 (even though technically the 52 : schema supports it). 53 : 54 : See: https://github.com/anza-xyz/agave/blob/v3.1/gossip/src/duplicate_shred_handler.rs#L21 */ 55 : 56 0 : #define FD_EQVOC_CHUNK_CNT (3) 57 : 58 : /* FD_EQVOC_CHUNK_SZ: the size of data in each chunk Firedancer produces 59 : in a DuplicateShred message is derived below. 60 : 61 : IPv6 MTU - IP / UDP headers = 1232 62 : DuplicateShredMaxPayloadSize = 1232 - 115 63 : DuplicateShred headers = 63 64 : 65 : See: https://github.com/anza-xyz/agave/blob/v2.0.3/gossip/src/cluster_info.rs#L113 */ 66 : 67 0 : #define FD_EQVOC_CHUNK_SZ (1232UL - 115UL - 63UL) 68 : FD_STATIC_ASSERT( FD_EQVOC_CHUNK_SZ<=sizeof(((fd_gossip_duplicate_shred_t*)0)->chunk), "DuplicateShred chunk max mismatch" ); 69 : 70 : /* FD_EQVOC_CHUNK{0,1,2}_LEN: the chunk lengths for each of the 3 chunks 71 : in a DuplicateShred proof. The memory layout is: 72 : 73 : [ shred1_sz (8 bytes) | shred1 | shred2_sz (8 bytes) | shred2 ] 74 : 75 : Chunks 0 and 1 are always FD_EQVOC_CHUNK_SZ bytes. Chunk 2 gets 76 : whatever bytes remain, which depends on the shred types: 77 : 78 : CC = code + code (both FD_SHRED_MAX_SZ) 79 : DD = data + data (both FD_SHRED_MIN_SZ) 80 : DC = data + code (FD_SHRED_MIN_SZ + FD_SHRED_MAX_SZ) 81 : CD = same as above, reversed 82 : 83 : Firedancer is particularly strict with the validation of duplicate 84 : shred chunks. Even though the schema supports both a variable-length 85 : and a variable-number of chunks, Agave restricts duplicate shred msgs 86 : to have 3 chunks (as mentioned above). Firedancer chooses to further 87 : restrict chunks to exactly how vanilla Agave implements serialization 88 : (there is no reason for an honest sender to "mod" the code nor reason 89 : to even support variable-length in the schema in the first place). 90 : 91 : Firedancer might miss a valid modded duplicate shred proof, but their 92 : proof would propagate from other validators too (and gossip tx is 93 : unreliable and not strictly required for the protocol to work). 94 : 95 : Agave validation: https://github.com/anza-xyz/agave/blob/v3.1/gossip/src/duplicate_shred.rs#L262-L268 */ 96 : 97 0 : #define FD_EQVOC_CHUNK0_LEN FD_EQVOC_CHUNK_SZ 98 : #define FD_EQVOC_CHUNK1_LEN FD_EQVOC_CHUNK_SZ 99 : #define FD_EQVOC_CHUNK2_LEN_CC (2UL * sizeof(ulong) + 2UL * FD_SHRED_MAX_SZ - 2UL * FD_EQVOC_CHUNK_SZ) 100 : #define FD_EQVOC_CHUNK2_LEN_DD (2UL * sizeof(ulong) + 2UL * FD_SHRED_MIN_SZ - 2UL * FD_EQVOC_CHUNK_SZ) 101 : #define FD_EQVOC_CHUNK2_LEN_DC (2UL * sizeof(ulong) + FD_SHRED_MIN_SZ + FD_SHRED_MAX_SZ - 2UL * FD_EQVOC_CHUNK_SZ) 102 : #define FD_EQVOC_CHUNK2_LEN_CD (FD_EQVOC_CHUNK2_LEN_DC) 103 : 104 : typedef struct fd_eqvoc fd_eqvoc_t; 105 : 106 : /* fd_eqvoc_{align,footprint} return the required alignment and 107 : footprint of a memory region suitable for use as eqvoc with up to 108 : shred_max shreds and chunk_max chunks. */ 109 : 110 : FD_FN_CONST ulong 111 : fd_eqvoc_align( void ); 112 : 113 : FD_FN_CONST ulong 114 : fd_eqvoc_footprint( ulong dup_max, 115 : ulong fec_max, 116 : ulong per_vtr_max, 117 : ulong vtr_max ); 118 : 119 : /* fd_eqvoc_new formats an unused memory region for use as a eqvoc. 120 : mem is a non-NULL pointer to this region in the local address space 121 : with the required footprint and alignment. */ 122 : 123 : void * 124 : fd_eqvoc_new( void * shmem, 125 : ulong dup_max, 126 : ulong fec_max, 127 : ulong per_vtr_max, 128 : ulong vtr_max, 129 : ulong seed ); 130 : 131 : /* fd_eqvoc_join joins the caller to the eqvoc. eqvoc points to the 132 : first byte of the memory region backing the eqvoc in the caller's 133 : address space. 134 : 135 : Returns a pointer in the local address space to eqvoc on success. */ 136 : 137 : fd_eqvoc_t * 138 : fd_eqvoc_join( void * sheqvoc ); 139 : 140 : /* fd_eqvoc_leave leaves a current local join. Returns a pointer to the 141 : underlying shared memory region on success and NULL on failure (logs 142 : details). Reasons for failure include eqvoc is NULL. */ 143 : 144 : void * 145 : fd_eqvoc_leave( fd_eqvoc_t const * eqvoc ); 146 : 147 : /* fd_eqvoc_delete unformats a memory region used as a eqvoc. Assumes 148 : nobody is joined to the region. Returns a pointer to the underlying 149 : shared memory region or NULL if used obviously in error (e.g. eqvoc 150 : is obviously not a eqvoc ... logs details). The ownership of the 151 : memory region is transferred to the caller. */ 152 : 153 : void * 154 : fd_eqvoc_delete( void * sheqvoc ); 155 : 156 : /* fd_eqvoc_shred_insert inserts the shred into eqvoc. Assumes that 157 : shreds are already sig-verified. Returns FD_EQVOC_SUCCESS if no 158 : equivocation was detected, FD_EQVOC_SUCCESS_{...} (positive) if the 159 : shred conflicts with a previously inserted shred (chunks_out will be 160 : populated with a DuplicateShred proof that can be sent over gossip), 161 : or FD_EQVOC_ERR_IGNORED_SLOT if shred->slot < root. */ 162 : 163 : int 164 : fd_eqvoc_shred_insert( fd_eqvoc_t * eqvoc, 165 : ushort shred_version, 166 : ulong root, 167 : fd_shred_t const * shred, 168 : fd_gossip_duplicate_shred_t chunks_out[static FD_EQVOC_CHUNK_CNT] ); 169 : 170 : /* fd_eqvoc_chunk_insert inserts a DuplicateShred chunk from gossip into 171 : eqvoc. Returns FD_EQVOC_SUCCESS if no proof was completed or 172 : verified yet, FD_EQVOC_SUCCESS_{...} (positive) if a complete proof 173 : was assembled and verified (chunks_out will be populated with the 174 : proof), or FD_EQVOC_ERR_{...} (negative) if the chunk or reassembled 175 : shreds failed validation. 176 : 177 : Chunks arrive from untrusted gossip peers and are validated (chunk 178 : count, index, length, shred deserialization, shred version, merkle 179 : root, signature, etc.). 180 : 181 : Returns FD_EQVOC_ERR_IGNORED_SLOT if leader_schedule is NULL or 182 : chunk->slot < root. Returns FD_EQVOC_ERR_IGNORED_FROM if from is not 183 : in the voter set. Each voter is limited to dup_max in-progress 184 : proofs; if exceeded, the LRU-proof is evicted. Once all chunks for a 185 : proof arrive, the proof is reassembled, verified, and released 186 : regardless of the outcome. */ 187 : 188 : int 189 : fd_eqvoc_chunk_insert( fd_eqvoc_t * eqvoc, 190 : ushort shred_version, 191 : ulong root, 192 : fd_epoch_leaders_t const * leader_schedule, 193 : fd_pubkey_t const * from, 194 : fd_gossip_duplicate_shred_t const * chunk, 195 : fd_gossip_duplicate_shred_t chunks_out[static FD_EQVOC_CHUNK_CNT] ); 196 : 197 : /* fd_eqvoc_query returns 1 if equivocation has been detected for the 198 : given slot (ie. a verified duplicate proof exists), 0 otherwise. */ 199 : 200 : int 201 : fd_eqvoc_query( fd_eqvoc_t * eqvoc, 202 : ulong slot ); 203 : 204 : /* fd_eqvoc_update_voters updates the vtr_map to match the given voter 205 : set. Removes entries not in id_keys[0..cnt) (and evicts their proofs), 206 : adds entries in id_keys not yet in vtr_map. Preserves existing 207 : entries that are still voters (keeping in-progress proofs intact). 208 : id_keys is an array of identity pubkeys of length cnt. */ 209 : 210 : void 211 : fd_eqvoc_update_voters( fd_eqvoc_t * eqvoc, 212 : fd_pubkey_t const * id_keys, 213 : ulong cnt ); 214 : 215 : #endif /* HEADER_fd_src_choreo_eqvoc_fd_eqvoc_h */