LCOV - code coverage report
Current view: top level - choreo/eqvoc - fd_eqvoc.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 4 25 16.0 %
Date: 2026-05-29 08:13:28 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          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 */

Generated by: LCOV version 1.14