LCOV - code coverage report
Current view: top level - choreo/eqvoc - fd_eqvoc.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 20 0.0 %
Date: 2026-05-19 08:05:42 Functions: 0 0 -

          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 */

Generated by: LCOV version 1.14