LCOV - code coverage report
Current view: top level - choreo/eqvoc - fd_eqvoc.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 15 20 75.0 %
Date: 2026-10-22 04:28:21 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          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 */

Generated by: LCOV version 1.14