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