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