LCOV - code coverage report
Current view: top level - choreo/eqvoc - fd_eqvoc.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 377 439 85.9 %
Date: 2026-09-17 04:28:31 Functions: 18 28 64.3 %

          Line data    Source code
       1             : #include "fd_eqvoc.h"
       2             : #include "../fd_choreo_base.h"
       3             : #include "../../ballet/shred/fd_shred.h"
       4             : #include "../../disco/shred/fd_fec_set.h"
       5             : 
       6             : /* fd_eqvoc maintains four bounded maps:
       7             : 
       8             :    dup_map  (capacity dup_max): maps slot -> equivocation result,
       9             :             recording slots we've already verified are duplicates
      10             :             (equivocations).  LRU-evicts when at capacity: querying an
      11             :             entry moves it to the tail of the recency list; when an
      12             :             insert is needed and the map is full, the head
      13             :             (least-recently-used) entry is evicted.
      14             : 
      15             :    fec_map  (capacity fec_max): maps (slot, fec_set_idx) -> shred,
      16             :             storing the first shred seen in each FEC set so it can be
      17             :             compared against later siblings for equivocation.  Same LRU
      18             :             eviction policy as dup_map.  Entries are also explicitly
      19             :             removed when equivocation is confirmed (proof constructed).
      20             : 
      21             :    prf_map  (capacity per_vtr_max * vtr_max): maps (slot, voter index)
      22             :             -> in-progress proof ("chunks") assembly state, tracking
      23             :             proofs per voter per slot.  The voter index refers into
      24             :             vtr_pool; all of a voter's proofs are removed before that
      25             :             index is reused.  Entries are LRU-evicted per voter when
      26             :             that voter's in-progress proof count reaches per_vtr_max.
      27             :             Entries are also removed when proof assembly completes (all
      28             :             chunks received), regardless of verification outcome, or
      29             :             when the corresponding voter is removed from vtr_map.
      30             : 
      31             :    vtr_map  (capacity vtr_max): maps voter pubkey -> per-voter proof-
      32             :             assembly state.  vtr entries are not evicted automatically;
      33             :             they are explicitly inserted and removed by
      34             :             fd_eqvoc_update_voters when the epoch stake set changes.
      35             :             Each vtr has a pre-allocated prf_dlist that tracks
      36             :             in-progress proofs for that voter.  Each voter's in-progress
      37             :             proof count is bounded by per_vtr_max; when that limit is
      38             :             reached, the oldest proof for that voter is evicted.
      39             : 
      40             :             dup_map                         fec_map
      41             :      map[0] +--------------------+   map[0] +--------------------+
      42             :             | (dup_t) {          |          | (fec_t) {          |
      43             :             |   .slot = 1,       |          |   .key  = 5|0,     |
      44             :             |   ...              |          |   ...              |
      45             :             | }                  |          | }                  |
      46             :      map[1] +--------------------+   map[1] +--------------------+
      47             :             | (dup_t) {          |          | (fec_t) {          |
      48             :             |   .slot = 2,       |          |   .key  = 6|0,     |
      49             :             |   ...              |          |   ...              |
      50             :             | }                  |          | }                  |
      51             :             +--------------------+          +--------------------+
      52             : 
      53             :             vtr_map                         prf_map
      54             :      map[0] +--------------------+   map[0] +--------------------+
      55             :             | (vtr_t) {          |          | (prf_t) {          |
      56             :             |   .from = X,       |          |   .key.slot = 5,   |
      57             :             |   ...              |          |   .key.vtr_idx = 1,|<-----+
      58             :             |   ...              |          |   ...              |      |
      59             :             | }                  |          | }                  |      |
      60             :      map[1] +--------------------+   map[1] +--------------------+      |
      61             :             | (vtr_t) {          |          | (prf_t) {          |      |
      62             :             |   .from = Y,       |          |   .key.slot = 6,   |      |
      63             :             |   ...              |          |   .key.vtr_idx = 1,|<--+  |
      64             :             |   .prf_dlist = +   |          |   ...              |   |  |
      65             :             | }              |   |          | }                  |   |  |
      66             :             +----------------|---+          +--------------------+   |  |
      67             :                              |                                       |  |
      68             :                              |                                       |  |
      69             :                              |              +------------------------+  |
      70             :                              |              |                           |
      71             :                              V              |        +------------------+
      72             :                              prf_dlist      |        |
      73             :                              +---------+---------+---------+
      74             :                              | (prf_t) | (prf_t) | (prf_t) |
      75             :                              |   ...   |   ...   |   ...   |
      76             :                              | }       | }       | }       |
      77             :                              +---------+---------+---------+
      78             :                              oldest                   newest
      79             : 
      80             :    Each vtr_t owns a prf_dlist of in-progress proofs (prf_t).
      81             :    prf_t elements are also in the global prf_map for lookup by
      82             :    (slot, vtr_idx).  When prf_dlist_cnt == per_vtr_max, the oldest
      83             :    prf is evicted from both prf_dlist and prf_map. */
      84             : 
      85             : typedef struct {
      86             :   ulong slot;
      87             :   uint next; /* pool next */
      88             :   struct {
      89             :     uint prev;
      90             :     uint next;
      91             :   } map;
      92             :   struct {
      93             :     uint prev;
      94             :     uint next;
      95             :   } dlist;
      96             : } dup_t;
      97             : 
      98             : #define POOL_NAME dup_pool
      99             : #define POOL_LAZY 1
     100         156 : #define POOL_T    dup_t
     101             : #define POOL_IDX_T uint
     102             : #include "../../util/tmpl/fd_pool.c"
     103             : 
     104             : #define MAP_NAME                           dup_map
     105           0 : #define MAP_ELE_T                          dup_t
     106          60 : #define MAP_KEY                            slot
     107          60 : #define MAP_PREV                           map.prev
     108          60 : #define MAP_NEXT                           map.next
     109         864 : #define MAP_IDX_T                          uint
     110             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     111             : #include "../../util/tmpl/fd_map_chain.c"
     112             : 
     113             : #define DLIST_NAME  dup_dlist
     114             : #define DLIST_ELE_T dup_t
     115         180 : #define DLIST_PREV  dlist.prev
     116         180 : #define DLIST_NEXT  dlist.next
     117             : #define DLIST_IDX_T uint
     118             : #include "../../util/tmpl/fd_dlist.c"
     119             : 
     120             : typedef struct {
     121             :   ulong key;  /* 32 bits = slot | 32 lsb = fec_set_idx  */
     122             :   uint next; /* pool next */
     123             :   struct {
     124             :     uint prev;
     125             :     uint next;
     126             :   } map;
     127             :   struct {
     128             :     uint prev;
     129             :     uint next;
     130             :   } dlist;
     131             :   union {
     132             :     fd_shred_t sample_shred;                  /* highest shred seen so far, by index */
     133             :     uchar      sample_bytes[FD_SHRED_MAX_SZ]; /* entire shred, both header and payload */
     134             :   };
     135             : } fec_t;
     136             : 
     137             : #define POOL_NAME fec_pool
     138             : #define POOL_LAZY 1
     139         156 : #define POOL_T    fec_t
     140             : #define POOL_IDX_T uint
     141             : #include "../../util/tmpl/fd_pool.c"
     142             : 
     143             : #define MAP_NAME                           fec_map
     144          48 : #define MAP_ELE_T                          fec_t
     145         168 : #define MAP_PREV                           map.prev
     146         315 : #define MAP_NEXT                           map.next
     147         336 : #define MAP_IDX_T                          uint
     148             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     149             : #include "../../util/tmpl/fd_map_chain.c"
     150             : 
     151             : #define DLIST_NAME  fec_dlist
     152             : #define DLIST_ELE_T fec_t
     153          66 : #define DLIST_PREV  dlist.prev
     154         114 : #define DLIST_NEXT  dlist.next
     155             : #define DLIST_IDX_T uint
     156             : #include "../../util/tmpl/fd_dlist.c"
     157             : 
     158             : typedef struct {
     159             :   ulong slot;
     160             :   uint  vtr_idx;
     161             : } xid_t;
     162             : 
     163             : struct prf {
     164             :   xid_t key;
     165             :   uint next;
     166             :   struct {
     167             :     uint prev;
     168             :     uint next;
     169             :   } map;
     170             :   struct {
     171             :     uint prev;
     172             :     uint next;
     173             :   } dlist;
     174             :   uchar  chunk_cnt;
     175             :   uchar  chunk_idx[ FD_EQVOC_CHUNK_CNT - 1 ];
     176             :   ushort chunk_len[ FD_EQVOC_CHUNK_CNT - 1 ];
     177             :   uchar  chunks   [ FD_EQVOC_CHUNK_CNT - 1 ][ FD_EQVOC_CHUNK_SZ ];
     178             : };
     179             : typedef struct prf prf_t;
     180             : 
     181             : #define POOL_NAME prf_pool
     182             : #define POOL_LAZY 1
     183         156 : #define POOL_T    prf_t
     184             : #define POOL_IDX_T uint
     185             : #include "../../util/tmpl/fd_pool.c"
     186             : 
     187             : #define MAP_NAME                           prf_map
     188          90 : #define MAP_ELE_T                          prf_t
     189             : #define MAP_KEY_T                          xid_t
     190         144 : #define MAP_PREV                           map.prev
     191         240 : #define MAP_NEXT                           map.next
     192         825 : #define MAP_IDX_T                          uint
     193         201 : #define MAP_KEY_EQ(k0,k1)                  ((((k0)->slot)==((k1)->slot)) & (((k0)->vtr_idx)==((k1)->vtr_idx)))
     194         519 : #define MAP_KEY_HASH(key,seed)             fd_ulong_hash( ((key)->slot) ^ ((key)->vtr_idx) ^ (seed) )
     195             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     196             : #include "../../util/tmpl/fd_map_chain.c"
     197             : 
     198             : #define DLIST_NAME  prf_dlist
     199             : #define DLIST_ELE_T prf_t
     200         495 : #define DLIST_PREV  dlist.prev
     201         519 : #define DLIST_NEXT  dlist.next
     202             : #define DLIST_IDX_T uint
     203             : #include "../../util/tmpl/fd_dlist.c"
     204             : 
     205             : struct vtr {
     206             :   fd_pubkey_t from;
     207             :   uint        next; /* pool next; reused as kept flag during update_voters */
     208             :   struct {
     209             :     uint prev;
     210             :     uint next;
     211             :   } map;
     212             :   struct {
     213             :     uint prev;
     214             :     uint next;
     215             :   } dlist;
     216             :   ulong         prf_dlist_cnt;
     217             :   prf_dlist_t * prf_dlist;
     218             : };
     219             : typedef struct vtr vtr_t;
     220             : 
     221             : #define POOL_NAME vtr_pool
     222             : #define POOL_LAZY 1
     223         234 : #define POOL_T    vtr_t
     224             : #define POOL_IDX_T uint
     225             : #include "../../util/tmpl/fd_pool.c"
     226             : 
     227             : #define MAP_NAME                           vtr_map
     228          15 : #define MAP_ELE_T                          vtr_t
     229             : #define MAP_KEY_T                          fd_pubkey_t
     230          96 : #define MAP_KEY                            from
     231         303 : #define MAP_PREV                           map.prev
     232         558 : #define MAP_NEXT                           map.next
     233        1239 : #define MAP_IDX_T                          uint
     234         639 : #define MAP_KEY_EQ(k0,k1)                  (!memcmp((k0)->key,(k1)->key,sizeof(fd_pubkey_t)))
     235         630 : #define MAP_KEY_HASH(key,seed)             ((ulong)((key)->ul[1]^(seed)))
     236             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     237             : #include "../../util/tmpl/fd_map_chain.c"
     238             : 
     239             : #define DLIST_NAME  vtr_dlist
     240             : #define DLIST_ELE_T vtr_t
     241         153 : #define DLIST_PREV  dlist.prev
     242         219 : #define DLIST_NEXT  dlist.next
     243             : #define DLIST_IDX_T uint
     244             : #include "../../util/tmpl/fd_dlist.c"
     245             : 
     246             : struct fd_eqvoc {
     247             : 
     248             :   /* copy */
     249             : 
     250             :   ulong dup_max;
     251             :   ulong fec_max;
     252             :   ulong per_vtr_max;
     253             :   ulong vtr_max;
     254             : 
     255             :   /* owned */
     256             : 
     257             :   fd_sha512_t * sha512;
     258             :   void *        bmtree_mem;
     259             :   dup_t *       dup_pool;
     260             :   dup_map_t *   dup_map;
     261             :   dup_dlist_t * dup_dlist;
     262             :   fec_t *       fec_pool;
     263             :   fec_map_t *   fec_map;
     264             :   fec_dlist_t * fec_dlist;
     265             :   prf_t *       prf_pool;
     266             :   prf_map_t *   prf_map;
     267             :   vtr_t *       vtr_pool;
     268             :   vtr_map_t *   vtr_map;
     269             :   vtr_dlist_t * vtr_dlist;
     270             : };
     271             : typedef struct fd_eqvoc fd_eqvoc_t;
     272             : 
     273             : ulong
     274         702 : fd_eqvoc_align( void ) {
     275         702 :   return 128UL;
     276         702 : }
     277             : 
     278             : ulong
     279             : fd_eqvoc_footprint( ulong dup_max,
     280             :                     ulong fec_max,
     281             :                     ulong per_vtr_max,
     282         165 :                     ulong vtr_max ) {
     283             : 
     284         165 :   dup_max       = fd_ulong_pow2_up( dup_max );
     285         165 :   fec_max       = fd_ulong_pow2_up( fec_max );
     286         165 :   per_vtr_max   = fd_ulong_pow2_up( per_vtr_max );
     287         165 :   vtr_max       = fd_ulong_pow2_up( vtr_max );
     288         165 :   if( FD_UNLIKELY( !dup_max     || dup_max>UINT_MAX ||
     289         165 :                    !fec_max     || fec_max>UINT_MAX ||
     290         165 :                    !per_vtr_max || !vtr_max         || vtr_max>UINT_MAX ||
     291         165 :                    per_vtr_max>UINT_MAX/vtr_max ) ) return 0UL;
     292         156 :   ulong prf_max = per_vtr_max * vtr_max;
     293             : 
     294         156 :   ulong l = FD_LAYOUT_INIT;
     295         156 :   l = FD_LAYOUT_APPEND( l, alignof(fd_eqvoc_t),    sizeof(fd_eqvoc_t)                                      );
     296         156 :   l = FD_LAYOUT_APPEND( l, fd_sha512_align(),      fd_sha512_footprint()                                   );
     297         156 :   l = FD_LAYOUT_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT( FD_SHRED_MERKLE_LAYER_CNT ) );
     298         156 :   l = FD_LAYOUT_APPEND( l, dup_pool_align(),       dup_pool_footprint( dup_max )                           );
     299         156 :   l = FD_LAYOUT_APPEND( l, dup_map_align(),        dup_map_footprint( dup_map_chain_cnt_est( dup_max ) )   );
     300         156 :   l = FD_LAYOUT_APPEND( l, dup_dlist_align(),      dup_dlist_footprint()                                   );
     301         156 :   l = FD_LAYOUT_APPEND( l, fec_pool_align(),       fec_pool_footprint( fec_max )                           );
     302         156 :   l = FD_LAYOUT_APPEND( l, fec_map_align(),        fec_map_footprint( fec_map_chain_cnt_est( fec_max ) )   );
     303         156 :   l = FD_LAYOUT_APPEND( l, fec_dlist_align(),      fec_dlist_footprint()                                   );
     304         156 :   l = FD_LAYOUT_APPEND( l, prf_pool_align(),       prf_pool_footprint( prf_max )                           );
     305         156 :   l = FD_LAYOUT_APPEND( l, prf_map_align(),        prf_map_footprint( prf_map_chain_cnt_est( prf_max ) )   );
     306         156 :   l = FD_LAYOUT_APPEND( l, vtr_pool_align(),       vtr_pool_footprint( vtr_max )                           );
     307         156 :   l = FD_LAYOUT_APPEND( l, vtr_map_align(),        vtr_map_footprint( vtr_map_chain_cnt_est( vtr_max ) )   );
     308         156 :   l = FD_LAYOUT_APPEND( l, vtr_dlist_align(),      vtr_dlist_footprint()                                   );
     309         780 :   for( ulong i = 0UL; i < vtr_max; i++ ) {
     310         624 :     l = FD_LAYOUT_APPEND( l, prf_dlist_align(), prf_dlist_footprint() );
     311         624 :   }
     312         156 :   return FD_LAYOUT_FINI( l, fd_eqvoc_align() );
     313         165 : }
     314             : 
     315             : void *
     316             : fd_eqvoc_new( void * shmem,
     317             :               ulong  dup_max,
     318             :               ulong  fec_max,
     319             :               ulong  per_vtr_max,
     320             :               ulong  vtr_max,
     321          78 :               ulong  seed ) {
     322             : 
     323          78 :   if( FD_UNLIKELY( !shmem ) ) {
     324           0 :     FD_LOG_WARNING(( "NULL mem" ));
     325           0 :     return NULL;
     326           0 :   }
     327             : 
     328          78 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_eqvoc_align() ) ) ) {
     329           0 :     FD_LOG_WARNING(( "misaligned mem" ));
     330           0 :     return NULL;
     331           0 :   }
     332             : 
     333          78 :   ulong footprint = fd_eqvoc_footprint( dup_max, fec_max, per_vtr_max, vtr_max );
     334          78 :   if( FD_UNLIKELY( !footprint ) ) {
     335           0 :     FD_LOG_WARNING(( "bad dup_max (%lu), fec_max (%lu), or vtr_max (%lu)", dup_max, fec_max, vtr_max ));
     336           0 :     return NULL;
     337           0 :   }
     338             : 
     339          78 :   dup_max       = fd_ulong_pow2_up( dup_max );
     340          78 :   fec_max       = fd_ulong_pow2_up( fec_max );
     341          78 :   per_vtr_max   = fd_ulong_pow2_up( per_vtr_max );
     342          78 :   vtr_max       = fd_ulong_pow2_up( vtr_max );
     343          78 :   ulong prf_max = per_vtr_max * vtr_max;
     344             : 
     345          78 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     346          78 :   void * eqvoc_mem  = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_eqvoc_t),    sizeof(fd_eqvoc_t)                                      );
     347          78 :   void * sha512     = FD_SCRATCH_ALLOC_APPEND( l, fd_sha512_align(),      fd_sha512_footprint()                                   );
     348          78 :   void * bmtree_mem = FD_SCRATCH_ALLOC_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT( FD_SHRED_MERKLE_LAYER_CNT ) );
     349          78 :   void * dup_pool   = FD_SCRATCH_ALLOC_APPEND( l, dup_pool_align(),       dup_pool_footprint( dup_max )                           );
     350          78 :   void * dup_map    = FD_SCRATCH_ALLOC_APPEND( l, dup_map_align(),        dup_map_footprint( dup_map_chain_cnt_est( dup_max ) )   );
     351          78 :   void * dup_dlist  = FD_SCRATCH_ALLOC_APPEND( l, dup_dlist_align(),      dup_dlist_footprint()                                   );
     352          78 :   void * fec_pool   = FD_SCRATCH_ALLOC_APPEND( l, fec_pool_align(),       fec_pool_footprint( fec_max )                           );
     353          78 :   void * fec_map    = FD_SCRATCH_ALLOC_APPEND( l, fec_map_align(),        fec_map_footprint( fec_map_chain_cnt_est( fec_max ) )   );
     354          78 :   void * fec_dlist  = FD_SCRATCH_ALLOC_APPEND( l, fec_dlist_align(),      fec_dlist_footprint()                                   );
     355          78 :   void * prf_pool   = FD_SCRATCH_ALLOC_APPEND( l, prf_pool_align(),       prf_pool_footprint( prf_max )                           );
     356          78 :   void * prf_map    = FD_SCRATCH_ALLOC_APPEND( l, prf_map_align(),        prf_map_footprint( prf_map_chain_cnt_est( prf_max ) )   );
     357          78 :   void * vtr_pool   = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(),       vtr_pool_footprint( vtr_max )                           );
     358          78 :   void * vtr_map    = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(),        vtr_map_footprint( vtr_map_chain_cnt_est( vtr_max ) )   );
     359          78 :   void * vtr_dlist  = FD_SCRATCH_ALLOC_APPEND( l, vtr_dlist_align(),      vtr_dlist_footprint()                                   );
     360             : 
     361          78 :   fd_eqvoc_t * eqvoc = (fd_eqvoc_t *)eqvoc_mem;
     362          78 :   eqvoc->dup_max     = dup_max;
     363          78 :   eqvoc->fec_max     = fec_max;
     364          78 :   eqvoc->per_vtr_max = per_vtr_max;
     365          78 :   eqvoc->vtr_max     = vtr_max;
     366             : 
     367          78 :   eqvoc->sha512     = fd_sha512_new( sha512                                           );
     368          78 :   eqvoc->bmtree_mem = bmtree_mem;
     369          78 :   eqvoc->dup_pool   = dup_pool_new ( dup_pool, dup_max                                );
     370          78 :   eqvoc->dup_map    = dup_map_new  ( dup_map,  dup_map_chain_cnt_est( dup_max ), seed );
     371          78 :   eqvoc->dup_dlist  = dup_dlist_new( dup_dlist                                        );
     372          78 :   eqvoc->fec_pool   = fec_pool_new ( fec_pool, fec_max                                );
     373          78 :   eqvoc->fec_map    = fec_map_new  ( fec_map,  fec_map_chain_cnt_est( fec_max ), seed );
     374          78 :   eqvoc->fec_dlist  = fec_dlist_new( fec_dlist                                        );
     375          78 :   eqvoc->prf_pool   = prf_pool_new ( prf_pool, prf_max                                );
     376          78 :   eqvoc->prf_map    = prf_map_new  ( prf_map,  prf_map_chain_cnt_est( prf_max ), seed );
     377          78 :   eqvoc->vtr_pool   = vtr_pool_new ( vtr_pool, vtr_max                                );
     378          78 :   eqvoc->vtr_map    = vtr_map_new  ( vtr_map,  vtr_map_chain_cnt_est( vtr_max ), seed );
     379          78 :   eqvoc->vtr_dlist  = vtr_dlist_new( vtr_dlist                                        );
     380             : 
     381          78 :   vtr_t * pool_join = vtr_pool_join( eqvoc->vtr_pool );
     382         390 :   for( ulong i = 0UL; i < vtr_max; i++ ) {
     383         312 :     void * prf_dlist       = FD_SCRATCH_ALLOC_APPEND( l, prf_dlist_align(), prf_dlist_footprint() );
     384         312 :     pool_join[i].prf_dlist_cnt = 0;
     385         312 :     pool_join[i].prf_dlist     = prf_dlist_new( prf_dlist );
     386         312 :   }
     387          78 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_eqvoc_align() )==(ulong)shmem + footprint );
     388             : 
     389          78 :   return shmem;
     390          78 : }
     391             : 
     392             : fd_eqvoc_t *
     393          78 : fd_eqvoc_join( void * sheqvoc ) {
     394             : 
     395          78 :   if( FD_UNLIKELY( !sheqvoc ) ) {
     396           0 :     FD_LOG_WARNING(( "NULL eqvoc" ));
     397           0 :     return NULL;
     398           0 :   }
     399             : 
     400          78 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)sheqvoc, fd_eqvoc_align() ) ) ) {
     401           0 :     FD_LOG_WARNING(( "misaligned eqvoc" ));
     402           0 :     return NULL;
     403           0 :   }
     404             : 
     405          78 :   fd_eqvoc_t * eqvoc = (fd_eqvoc_t *)sheqvoc;
     406          78 :   eqvoc->sha512      = fd_sha512_join( eqvoc->sha512    );
     407             :   /* bmtree */
     408          78 :   eqvoc->dup_pool    = dup_pool_join ( eqvoc->dup_pool  );
     409          78 :   eqvoc->dup_map     = dup_map_join  ( eqvoc->dup_map   );
     410          78 :   eqvoc->dup_dlist   = dup_dlist_join( eqvoc->dup_dlist );
     411          78 :   eqvoc->fec_pool    = fec_pool_join ( eqvoc->fec_pool  );
     412          78 :   eqvoc->fec_map     = fec_map_join  ( eqvoc->fec_map   );
     413          78 :   eqvoc->fec_dlist   = fec_dlist_join( eqvoc->fec_dlist );
     414          78 :   eqvoc->prf_pool    = prf_pool_join ( eqvoc->prf_pool  );
     415          78 :   eqvoc->prf_map     = prf_map_join  ( eqvoc->prf_map   );
     416          78 :   eqvoc->vtr_pool    = vtr_pool_join ( eqvoc->vtr_pool  );
     417          78 :   eqvoc->vtr_map     = vtr_map_join  ( eqvoc->vtr_map   );
     418          78 :   eqvoc->vtr_dlist   = vtr_dlist_join( eqvoc->vtr_dlist );
     419         390 :   for( ulong i = 0UL; i < eqvoc->vtr_max; i++ ) {
     420         312 :     eqvoc->vtr_pool[i].prf_dlist = prf_dlist_join( eqvoc->vtr_pool[i].prf_dlist );
     421         312 :   }
     422             : 
     423          78 :   return (fd_eqvoc_t *)sheqvoc;
     424          78 : }
     425             : 
     426             : void *
     427          78 : fd_eqvoc_leave( fd_eqvoc_t const * eqvoc ) {
     428             : 
     429          78 :   if( FD_UNLIKELY( !eqvoc ) ) {
     430           0 :     FD_LOG_WARNING(( "NULL eqvoc" ));
     431           0 :     return NULL;
     432           0 :   }
     433             : 
     434          78 :   return (void *)eqvoc;
     435          78 : }
     436             : 
     437             : void *
     438          78 : fd_eqvoc_delete( void * eqvoc ) {
     439             : 
     440          78 :   if( FD_UNLIKELY( !eqvoc ) ) {
     441           0 :     FD_LOG_WARNING(( "NULL eqvoc" ));
     442           0 :     return NULL;
     443           0 :   }
     444             : 
     445          78 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)eqvoc, fd_eqvoc_align() ) ) ) {
     446           0 :     FD_LOG_WARNING(( "misaligned eqvoc" ));
     447           0 :     return NULL;
     448           0 :   }
     449             : 
     450          78 :   return eqvoc;
     451          78 : }
     452             : 
     453             : static dup_t *
     454             : dup_query( fd_eqvoc_t * eqvoc,
     455         363 :            ulong        slot ) {
     456         363 :   dup_t * dup = dup_map_ele_query( eqvoc->dup_map, &slot, NULL, eqvoc->dup_pool );
     457         363 :   if( FD_LIKELY( dup ) ) {
     458          60 :     dup_dlist_ele_remove( eqvoc->dup_dlist, dup, eqvoc->dup_pool );
     459          60 :     dup_dlist_ele_push_tail( eqvoc->dup_dlist, dup, eqvoc->dup_pool );
     460          60 :   }
     461         363 :   return dup;
     462         363 : }
     463             : 
     464             : static fec_t *
     465             : fec_query( fd_eqvoc_t * eqvoc,
     466             :            ulong        slot,
     467          66 :            ulong        fec_set_idx ) {
     468          66 :   ulong   key = slot << 32 | fec_set_idx;
     469          66 :   fec_t * fec = fec_map_ele_query( eqvoc->fec_map, &key, NULL, eqvoc->fec_pool );
     470          66 :   if( FD_LIKELY( fec ) ) {
     471           0 :     fec_dlist_ele_remove( eqvoc->fec_dlist, fec, eqvoc->fec_pool );
     472           0 :     fec_dlist_ele_push_tail( eqvoc->fec_dlist, fec, eqvoc->fec_pool );
     473           0 :   }
     474          66 :   return fec;
     475          66 : }
     476             : 
     477             : static prf_t *
     478             : prf_query( fd_eqvoc_t * eqvoc,
     479             :            vtr_t *      vtr,
     480         276 :            ulong        slot ) {
     481         276 :   xid_t   key = { .slot = slot, .vtr_idx = (uint)vtr_pool_idx( eqvoc->vtr_pool, vtr ) };
     482         276 :   prf_t * prf = prf_map_ele_query( eqvoc->prf_map, &key, NULL, eqvoc->prf_pool );
     483         276 :   if( FD_LIKELY( prf ) ) {
     484         156 :     prf_dlist_ele_remove( vtr->prf_dlist, prf, eqvoc->prf_pool );
     485         156 :     prf_dlist_ele_push_tail( vtr->prf_dlist, prf, eqvoc->prf_pool );
     486         156 :   }
     487         276 :   return prf;
     488         276 : }
     489             : 
     490             : static dup_t *
     491             : dup_insert( fd_eqvoc_t       * eqvoc,
     492          60 :             ulong              slot ) {
     493             : 
     494             :   /* FIFO evict if full.  Invariant: iff in dlist then in map / pool. */
     495             : 
     496          60 :   if( FD_UNLIKELY( !dup_pool_free( eqvoc->dup_pool ) ) ) {
     497           0 :     dup_t * dup = dup_dlist_ele_pop_head( eqvoc->dup_dlist, eqvoc->dup_pool );
     498           0 :     dup_map_ele_remove_fast( eqvoc->dup_map, dup, eqvoc->dup_pool );
     499           0 :     dup_pool_ele_release( eqvoc->dup_pool, dup );
     500           0 :   }
     501             : 
     502             :   /* Insert.  Invariant: pool free => map / dlist free. */
     503             : 
     504          60 :   dup_t * dup = dup_pool_ele_acquire( eqvoc->dup_pool );
     505          60 :   dup->slot   = slot;
     506          60 :   dup_map_ele_insert( eqvoc->dup_map, dup, eqvoc->dup_pool );
     507          60 :   dup_dlist_ele_push_tail( eqvoc->dup_dlist, dup, eqvoc->dup_pool );
     508          60 :   return dup;
     509          60 : }
     510             : 
     511             : static fec_t *
     512             : fec_insert( fd_eqvoc_t * eqvoc,
     513             :             ulong        slot,
     514          66 :             uint         fec_set_idx ) {
     515             : 
     516          66 :   ulong key = slot << 32 | fec_set_idx;
     517             : 
     518             :   /* FIFO evict if full.  Invariant: iff in dlist then in map / pool. */
     519             : 
     520          66 :   if( FD_UNLIKELY( !fec_pool_free( eqvoc->fec_pool ) ) ) {
     521          48 :     fec_t * fec = fec_dlist_ele_pop_head( eqvoc->fec_dlist, eqvoc->fec_pool );
     522          48 :     fec_map_ele_remove_fast( eqvoc->fec_map, fec, eqvoc->fec_pool );
     523          48 :     fec_pool_ele_release( eqvoc->fec_pool, fec );
     524          48 :   }
     525             : 
     526             :   /* Insert.  Invariant: pool free => map / dlist free. */
     527             : 
     528          66 :   fec_t * fec = fec_pool_ele_acquire( eqvoc->fec_pool );
     529          66 :   fec->key    = key;
     530          66 :   fec_map_ele_insert( eqvoc->fec_map, fec, eqvoc->fec_pool );
     531          66 :   fec_dlist_ele_push_tail( eqvoc->fec_dlist, fec, eqvoc->fec_pool );
     532          66 :   return fec;
     533          66 : }
     534             : 
     535             : static prf_t *
     536             : prf_insert( fd_eqvoc_t * eqvoc,
     537             :             vtr_t *      vtr,
     538         117 :             ulong        slot ) {
     539             : 
     540             :   /* Each from pubkey in gossip is limited to per_vtr_max proofs.
     541             :      If we receive more than per_vtr_max from one pubkey, FIFO evict.
     542             :      We group by pubkey to prevent a single pubkey from spamming
     543             :      junk proofs. */
     544             : 
     545         117 :   if( FD_UNLIKELY( vtr->prf_dlist_cnt==eqvoc->per_vtr_max ) ) {
     546           6 :     prf_t * evict = prf_dlist_ele_pop_head( vtr->prf_dlist, eqvoc->prf_pool );
     547           6 :     prf_map_ele_remove_fast( eqvoc->prf_map, evict, eqvoc->prf_pool );
     548           6 :     prf_pool_ele_release( eqvoc->prf_pool, evict );
     549           6 :     vtr->prf_dlist_cnt--;
     550           6 :   }
     551             : 
     552         117 :   xid_t   key = { .slot = slot, .vtr_idx = (uint)vtr_pool_idx( eqvoc->vtr_pool, vtr ) };
     553         117 :   prf_t * prf = prf_pool_ele_acquire( eqvoc->prf_pool );
     554         117 :   prf->key       = key;
     555         117 :   prf->chunk_cnt = 0;
     556         117 :   prf_map_ele_insert( eqvoc->prf_map, prf, eqvoc->prf_pool );
     557         117 :   prf_dlist_ele_push_tail( vtr->prf_dlist, prf, eqvoc->prf_pool );
     558         117 :   vtr->prf_dlist_cnt++;
     559         117 :   return prf;
     560         117 : }
     561             : 
     562             : static int
     563           0 : is_last_shred( fd_shred_t const * shred ) {
     564           0 :   return fd_shred_is_data( fd_shred_type( shred->variant ) ) && shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE;
     565           0 : }
     566             : 
     567             : /* construct_proof constructs a DuplicateShred proof from shred1 and
     568             :    shred2.  Assumes shred1 and shred2 have already been verified via
     569             :    verify_proof.  On return, chunks_out will be populated with the
     570             :    serialized format of the proof.
     571             : 
     572             :    [ shred1_sz (8 bytes) | shred1 | shred2_sz (8 bytes) | shred2 ]
     573             : 
     574             :    Caller supplies `chunks_out`, which is an array that MUST contain
     575             :    FD_EQVOC_CHUNK_CNT elements. */
     576             : 
     577             : static void
     578             : construct_proof( fd_shred_t const *          shred1,
     579             :                  fd_shred_t const *          shred2,
     580         111 :                  fd_gossip_duplicate_shred_t chunks_out[static FD_EQVOC_CHUNK_CNT] ) {
     581             : 
     582         444 :   for (uchar i = 0; i < FD_EQVOC_CHUNK_CNT; i++ ) {
     583         333 :     chunks_out[i].index       = i;
     584         333 :     chunks_out[i].slot        = shred1->slot;
     585         333 :     chunks_out[i].num_chunks  = FD_EQVOC_CHUNK_CNT;
     586         333 :     chunks_out[i].chunk_index = i;
     587         333 :   }
     588             : 
     589         111 :   ulong shred1_sz = fd_shred_sz( shred1 );
     590         111 :   ulong shred2_sz = fd_shred_sz( shred2 );
     591             : 
     592             :   /* Populate chunk0 */
     593             : 
     594         111 :   FD_STORE( ulong, chunks_out[0].chunk, shred1_sz );
     595         111 :   memcpy( chunks_out[0].chunk + sizeof(ulong), shred1, FD_EQVOC_CHUNK_SZ - sizeof(ulong) );
     596         111 :   chunks_out[0].chunk_len = FD_EQVOC_CHUNK_SZ;
     597             : 
     598             :   /* Populate chunk1 */
     599             : 
     600         111 :   ulong shred1_off = FD_EQVOC_CHUNK_SZ - sizeof(ulong);
     601         111 :   ulong shred1_rem = shred1_sz - shred1_off;
     602         111 :   memcpy( chunks_out[1].chunk, (uchar *)shred1 + shred1_off, shred1_rem );
     603         111 :   FD_STORE( ulong, chunks_out[1].chunk + shred1_rem, shred2_sz );
     604         111 :   ulong chunk1_off = shred1_rem + sizeof(ulong);
     605         111 :   ulong chunk1_rem = FD_EQVOC_CHUNK_SZ - chunk1_off;
     606         111 :   memcpy( chunks_out[1].chunk + chunk1_off, shred2, chunk1_rem );
     607         111 :   chunks_out[1].chunk_len = FD_EQVOC_CHUNK_SZ;
     608             : 
     609             :   /* Populate chunk2 */
     610             : 
     611         111 :   ulong shred2_off = chunk1_rem;
     612         111 :   ulong shred2_rem = shred2_sz - shred2_off;
     613         111 :   memcpy( chunks_out[2].chunk, (uchar *)shred2 + shred2_off, shred2_rem );
     614         111 :   chunks_out[2].chunk_len = shred2_rem;
     615         111 : }
     616             : 
     617             : /* verify_proof verifies that the two shreds contained in `proof` do in
     618             :    fact equivocate.  The two shreds came from untrusted gossip msgs, so
     619             :    it needs validation.
     620             : 
     621             :    Returns: FD_EQVOC_SUCCESS (1) if equivocation detected,
     622             :             FD_EQVOC_IGNORED (0) if not, or FD_EQVOC_ERR_{...} (<0) if
     623             :             the shreds were not valid inputs (untrusted path only).
     624             : 
     625             :    The implementation mirrors the Agave version very closely. See:
     626             :    https://github.com/anza-xyz/agave/blob/v3.1/gossip/src/duplicate_shred.rs#L137-L142
     627             : 
     628             :    Two shreds equivocate if they satisfy any of the following:
     629             : 
     630             :    1. Both shreds specify the same index and shred type, however their
     631             :       payloads differ.
     632             :    2. Both shreds specify the same FEC set, however their merkle roots
     633             :       differ.
     634             :    3. Both shreds specify the same FEC set and are coding shreds,
     635             :       however their erasure configs conflict.
     636             :    4. The shreds specify different FEC sets, the lower index shred is a
     637             :       coding shred, and its erasure meta indicates an FEC set overlap.
     638             :    5. The shreds specify different FEC sets, the lower index shred has a
     639             :       merkle root that is not equal to the chained merkle root of the
     640             :       higher index shred.
     641             :    6. The shreds are data shreds with different indices and the shred
     642             :       with the lower index has the LAST_SHRED_IN_SLOT flag set.
     643             : 
     644             :    Ref:
     645             :    https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0204-slashable-event-verification.md#proof-verification
     646             : 
     647             :    Note: two shreds are in the same FEC set if they have the same
     648             :    verified and FEC set index.
     649             : 
     650             :    To prevent false positives, this function also performs the following
     651             :    input validation on the shreds:
     652             : 
     653             :    1. shred1 and shred2 are for the same verified.
     654             :    2. shred1 and shred2 are both the expected shred_version.
     655             :    3. shred1 and shred2 are either chained merkle or chained resigned
     656             :       merkle variants.
     657             :    4. shred1 and shred2 contain valid signatures signed by the same
     658             :       producer pubkey.
     659             : 
     660             :    If any of the above input validation fails, this function returns
     661             :    FD_EQVOC_ERR_{...}. */
     662             : 
     663             : static int
     664             : verify_proof( fd_eqvoc_t *               eqvoc,
     665             :               ushort                     shred_version,
     666             :               fd_epoch_leaders_t const * leader_schedule,
     667             :               fd_shred_t const *         shred1,
     668          54 :               fd_shred_t const *         shred2 ) {
     669             : 
     670             :   /* Must be same slot. */
     671             : 
     672          54 :   if( FD_UNLIKELY( shred1->slot != shred2->slot ) ) return FD_EQVOC_ERR_SLOT;
     673             : 
     674             :   /* Must be same shred version. */
     675             : 
     676          54 :   if( FD_UNLIKELY( shred1->version != shred_version ) ) return FD_EQVOC_ERR_VERSION;
     677          54 :   if( FD_UNLIKELY( shred2->version != shred_version ) ) return FD_EQVOC_ERR_VERSION;
     678             : 
     679             :   /* Must be chained merkle shreds. */
     680             : 
     681          54 :   if( FD_UNLIKELY( !fd_shred_is_chained ( fd_shred_type( shred1->variant ) ) ) ) return FD_EQVOC_ERR_TYPE;
     682          54 :   if( FD_UNLIKELY( !fd_shred_is_chained ( fd_shred_type( shred2->variant ) ) ) ) return FD_EQVOC_ERR_TYPE;
     683             : 
     684             :   /* Must have valid merkle roots. */
     685             : 
     686          54 :   fd_bmtree_node_t mr1;
     687          54 :   fd_bmtree_node_t mr2;
     688          54 :   if( FD_UNLIKELY( !fd_shred_merkle_root( shred1, eqvoc->bmtree_mem, &mr1 ) ) ) return FD_EQVOC_ERR_MERKLE;
     689          54 :   if( FD_UNLIKELY( !fd_shred_merkle_root( shred2, eqvoc->bmtree_mem, &mr2 ) ) ) return FD_EQVOC_ERR_MERKLE;
     690             : 
     691             :   /* Must sigverify (if leader_schedule provided). */
     692             : 
     693          54 :   if( FD_LIKELY( leader_schedule ) ) {
     694           0 :     fd_pubkey_t const * leader = fd_epoch_leaders_get( leader_schedule, shred1->slot );
     695           0 :     if( FD_UNLIKELY( !leader ) ) return FD_EQVOC_ERR_SIG;
     696           0 :     fd_sha512_t  _sha512[1];
     697           0 :     fd_sha512_t * sha512 = fd_sha512_join( fd_sha512_new( _sha512 ) );
     698           0 :     if( FD_UNLIKELY( FD_ED25519_SUCCESS != fd_ed25519_verify( mr1.hash, 32UL, shred1->signature, leader->uc, sha512 ) ||
     699           0 :                      FD_ED25519_SUCCESS != fd_ed25519_verify( mr2.hash, 32UL, shred2->signature, leader->uc, sha512 ) ) ) {
     700           0 :       return FD_EQVOC_ERR_SIG;
     701           0 :     }
     702           0 :   }
     703             : 
     704             :   /* If both are data shreds, then check for last shred conflicts. */
     705             : 
     706          54 :   if( FD_LIKELY( fd_shred_is_data( fd_shred_type( shred1->variant ) ) && fd_shred_is_data( fd_shred_type( shred2->variant ) ) ) ) {
     707          18 :     if( FD_LIKELY( ( shred1->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE && shred2->idx > shred1->idx ) ||
     708          18 :                    ( shred2->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE && shred1->idx > shred2->idx ) ) ) {
     709           0 :       return FD_EQVOC_SUCCESS;
     710           0 :     }
     711          18 :   }
     712             : 
     713             :   /* If both shreds are in the same FEC set, then check for merkle root
     714             :      conflicts. */
     715             : 
     716          54 :   if( FD_UNLIKELY( shred1->fec_set_idx == shred2->fec_set_idx ) ) {
     717          54 :     if( FD_LIKELY( 0!=memcmp( mr1.hash, mr2.hash, sizeof(mr1.hash)) ) ) {
     718          54 :       return FD_EQVOC_SUCCESS;
     719          54 :     }
     720          54 :   }
     721             : 
     722           0 :   return FD_EQVOC_IGNORED;
     723          54 : }
     724             : 
     725             : int
     726             : fd_eqvoc_shred_insert( fd_eqvoc_t *                eqvoc,
     727             :                        int                         shred_hint,
     728             :                        fd_shred_t const *          shred,
     729          36 :                        fd_gossip_duplicate_shred_t chunks_out[static FD_EQVOC_CHUNK_CNT] ) {
     730             : 
     731          36 :   ulong   slot = shred->slot;
     732          36 :   dup_t * dup  = dup_query( eqvoc, slot );
     733          36 :   if( FD_UNLIKELY( dup ) ) return 0; /* no proof constructed */
     734             : 
     735          33 :   if( FD_UNLIKELY( shred_hint ) ) {
     736             : 
     737             :     /* shred tile has hinted to us that this shred equivocates */
     738             : 
     739           0 :     fec_t * fec = fec_query( eqvoc, shred->slot, shred->fec_set_idx );
     740           0 :     if( FD_UNLIKELY( !fec ) ) return 0; /* it's possible we already evicted this FEC set */
     741           0 :     construct_proof( &fec->sample_shred, shred, chunks_out );
     742           0 :     dup_insert( eqvoc, slot );
     743           0 :     fec_dlist_ele_remove( eqvoc->fec_dlist, fec, eqvoc->fec_pool );
     744           0 :     fec_map_ele_remove_fast( eqvoc->fec_map, fec, eqvoc->fec_pool );
     745           0 :     fec_pool_ele_release( eqvoc->fec_pool, fec );
     746           0 :     return 1; /* proof constructed */
     747             : 
     748          33 :   } else {
     749             : 
     750             :     /* last index conflicts are not hinted by shred */
     751             : 
     752          33 :     fec_t * last = fec_query( eqvoc, shred->slot, UINT_MAX );
     753          33 :     if( FD_UNLIKELY( !last ) ) {
     754          33 :       last = fec_insert( eqvoc, slot, UINT_MAX );  /* specially index the last shred in a slot */
     755          33 :       fd_memcpy( &last->sample_shred, shred, fd_shred_sz( shred ) );
     756          33 :     } else if( FD_UNLIKELY( ( is_last_shred( shred               ) && shred->idx < last->sample_shred.idx ) ||
     757           0 :                             ( is_last_shred( &last->sample_shred ) && shred->idx > last->sample_shred.idx ) ) ) {
     758           0 :       construct_proof( shred, &last->sample_shred, chunks_out );
     759           0 :       dup_insert( eqvoc, slot );
     760           0 :       return 1;
     761           0 :     } else if( FD_UNLIKELY( shred->idx > last->sample_shred.idx ) ) {
     762           0 :       fd_memcpy( &last->sample_shred, shred, fd_shred_sz( shred ) );
     763           0 :     }
     764             : 
     765          33 :     fec_t * fec = fec_query( eqvoc, shred->slot, shred->fec_set_idx );
     766          33 :     if( FD_UNLIKELY( !fec ) ) {
     767          33 :       fec = fec_insert( eqvoc, shred->slot, shred->fec_set_idx );
     768          33 :       fd_memcpy( &fec->sample_shred, shred, fd_shred_sz( shred ) );
     769          33 :     }
     770             : 
     771          33 :     return 0;
     772          33 :   }
     773          33 : }
     774             : 
     775             : int
     776             : fd_eqvoc_chunk_insert( fd_eqvoc_t                        * eqvoc,
     777             :                        ulong                               root,
     778             :                        ushort                              shred_version,
     779             :                        fd_epoch_leaders_t const          * leader_schedule,
     780             :                        fd_pubkey_t const                 * from,
     781             :                        fd_gossip_duplicate_shred_t const * chunk,
     782         315 :                        fd_gossip_duplicate_shred_t         chunks_out[static FD_EQVOC_CHUNK_CNT] ) {
     783             : 
     784         315 :   if( FD_UNLIKELY( chunk->slot <= root ) ) return FD_EQVOC_ERR_CHUNK_SLOT;
     785             : 
     786         312 :   vtr_t * vtr = vtr_map_ele_query( eqvoc->vtr_map, from, NULL, eqvoc->vtr_pool );
     787         312 :   if( FD_UNLIKELY( !vtr ) ) return FD_EQVOC_ERR_CHUNK_FROM;
     788             : 
     789         276 :   if( FD_UNLIKELY( chunk->num_chunks !=FD_EQVOC_CHUNK_CNT ) ) return FD_EQVOC_ERR_CHUNK_CNT;
     790         273 :   if( FD_UNLIKELY( chunk->chunk_index>=FD_EQVOC_CHUNK_CNT ) ) return FD_EQVOC_ERR_CHUNK_IDX;
     791             : 
     792         270 :   if( FD_UNLIKELY( chunk->chunk_index==0 && chunk->chunk_len!=FD_EQVOC_CHUNK0_LEN    ) ) return FD_EQVOC_ERR_CHUNK_LEN;
     793         267 :   if( FD_UNLIKELY( chunk->chunk_index==1 && chunk->chunk_len!=FD_EQVOC_CHUNK1_LEN    ) ) return FD_EQVOC_ERR_CHUNK_LEN;
     794         264 :   if( FD_UNLIKELY( chunk->chunk_index==2 && chunk->chunk_len!=FD_EQVOC_CHUNK2_LEN_CC &&
     795         264 :                                             chunk->chunk_len!=FD_EQVOC_CHUNK2_LEN_DD &&
     796         264 :                                             chunk->chunk_len!=FD_EQVOC_CHUNK2_LEN_DC &&
     797         264 :                                             chunk->chunk_len!=FD_EQVOC_CHUNK2_LEN_CD ) ) return FD_EQVOC_ERR_CHUNK_LEN;
     798             : 
     799         261 :   if( FD_UNLIKELY( dup_query( eqvoc, chunk->slot ) ) ) return FD_EQVOC_IGNORED; /* already verified an equivocation proof for this slot */
     800             : 
     801         261 :   prf_t * prf = prf_query( eqvoc, vtr, chunk->slot );
     802         261 :   if( FD_UNLIKELY( !prf ) ) prf = prf_insert( eqvoc, vtr, chunk->slot );
     803         465 :   for( uchar i = 0U; i < prf->chunk_cnt; i++ ) {
     804         213 :     if( FD_UNLIKELY( prf->chunk_idx[ i ]==chunk->chunk_index ) ) return FD_EQVOC_IGNORED;
     805         213 :   }
     806             : 
     807         252 :   if( FD_LIKELY( prf->chunk_cnt<FD_EQVOC_CHUNK_CNT-1 ) ) {
     808         186 :     uchar i = prf->chunk_cnt++;
     809         186 :     prf->chunk_idx[ i ] = chunk->chunk_index;
     810         186 :     prf->chunk_len[ i ] = (ushort)chunk->chunk_len;
     811         186 :     fd_memcpy( prf->chunks[ i ], chunk->chunk, chunk->chunk_len );
     812         186 :     return FD_EQVOC_IGNORED;
     813         186 :   }
     814             : 
     815          66 :   uchar buf[ 2 * FD_SHRED_MAX_SZ + 2 * sizeof(ulong) ] __attribute__((aligned(8)));
     816          66 :   fd_memcpy( buf + chunk->chunk_index * FD_EQVOC_CHUNK_SZ, chunk->chunk, chunk->chunk_len );
     817          66 :   ulong buf_sz = chunk->chunk_len;
     818         198 :   for( uchar i = 0U; i < prf->chunk_cnt; i++ ) {
     819         132 :     fd_memcpy( buf + prf->chunk_idx[ i ] * FD_EQVOC_CHUNK_SZ, prf->chunks[ i ], prf->chunk_len[ i ] );
     820         132 :     buf_sz += prf->chunk_len[ i ];
     821         132 :   }
     822             : 
     823          66 :   int err = FD_EQVOC_ERR_SERDE; ulong off = 0;
     824             : 
     825          66 :   if( FD_UNLIKELY( buf_sz - off < sizeof(ulong) ) ) goto cleanup;
     826          66 :   ulong shred1_sz = fd_ulong_load_8( buf );
     827          66 :   off += sizeof(ulong);
     828             : 
     829          66 :   if( FD_UNLIKELY( buf_sz - off < shred1_sz ) ) goto cleanup;
     830             :   /* We use FD_SHRED_BLK_MAX as max_shred_idx because that is what
     831             :      Agave does here (Shred::new_from_serialized_shred in into_shreds):
     832             :      https://github.com/anza-xyz/agave/blob/v4.2/gossip/src/duplicate_shred.rs#L350-L351 */
     833          66 :   fd_shred_t const * shred1 = fd_shred_parse( buf + off, shred1_sz, FD_SHRED_BLK_MAX );
     834          66 :   if( FD_UNLIKELY( !shred1 || fd_shred_sz( shred1 )!=shred1_sz ) ) goto cleanup; /* check the sz matches parsed shred's type */
     835          57 :   off += shred1_sz;
     836             : 
     837          57 :   if( FD_UNLIKELY( buf_sz - off < sizeof(ulong) ) ) goto cleanup;
     838          57 :   ulong shred2_sz = fd_ulong_load_8( buf + off );
     839          57 :   off += sizeof(ulong);
     840             : 
     841          57 :   if( FD_UNLIKELY( buf_sz - off < shred2_sz ) ) goto cleanup;
     842          57 :   fd_shred_t const * shred2 = fd_shred_parse( buf + off, shred2_sz, FD_SHRED_BLK_MAX );
     843          57 :   if( FD_UNLIKELY( !shred2 || fd_shred_sz( shred2 )!=shred2_sz ) ) goto cleanup; /* check the sz matches parsed shred's type */
     844          57 :   off += shred2_sz;
     845             : 
     846          57 :   if( FD_UNLIKELY( off!=buf_sz ) ) goto cleanup;
     847             : 
     848          57 :   if( FD_UNLIKELY( shred1->slot != chunk->slot || shred2->slot != chunk->slot ) ) goto cleanup;
     849             : 
     850          54 :   err = verify_proof( eqvoc, shred_version, leader_schedule, shred1, shred2 );
     851          54 :   if( FD_UNLIKELY( err==FD_EQVOC_SUCCESS ) ) {
     852          54 :     construct_proof( shred1, shred2, chunks_out );
     853          54 :     dup_insert( eqvoc, chunk->slot );
     854          54 :   }
     855             : 
     856          66 : cleanup:;
     857          66 :   prf_dlist_ele_remove( vtr->prf_dlist, prf, eqvoc->prf_pool );
     858          66 :   prf_map_ele_remove_fast( eqvoc->prf_map, prf, eqvoc->prf_pool );
     859          66 :   prf_pool_ele_release( eqvoc->prf_pool, prf );
     860          66 :   vtr->prf_dlist_cnt--;
     861          66 :   return err;
     862          54 : }
     863             : 
     864             : int
     865             : fd_eqvoc_proof_verified( fd_eqvoc_t * eqvoc,
     866          66 :                 ulong        slot ) {
     867          66 :   return !!dup_query( eqvoc, slot );
     868          66 : }
     869             : 
     870             : void
     871             : fd_eqvoc_update_voters( fd_eqvoc_t *        eqvoc,
     872             :                         fd_pubkey_t const * id_keys,
     873          27 :                         ulong               cnt ) {
     874             : 
     875          27 :   for( vtr_dlist_iter_t iter = vtr_dlist_iter_fwd_init( eqvoc->vtr_dlist, eqvoc->vtr_pool );
     876          66 :        !vtr_dlist_iter_done( iter, eqvoc->vtr_dlist, eqvoc->vtr_pool );
     877          39 :        iter = vtr_dlist_iter_fwd_next( iter, eqvoc->vtr_dlist, eqvoc->vtr_pool ) ) {
     878          39 :     eqvoc->vtr_pool[iter].next = 1; /* mark for removal */
     879          39 :   }
     880             : 
     881             :   /* First pass: unmark kept voters from being released. */
     882             : 
     883          87 :   for( ulong i=0UL; i<cnt; i++ ) {
     884          60 :     fd_pubkey_t const * id  = &id_keys[i];
     885          60 :     vtr_t *             vtr = vtr_map_ele_query( eqvoc->vtr_map, id, NULL, eqvoc->vtr_pool );
     886          60 :     if( FD_LIKELY( vtr ) ) {
     887          24 :       vtr_dlist_ele_remove( eqvoc->vtr_dlist, vtr, eqvoc->vtr_pool );
     888          24 :       vtr->next = 0; /* unmark for removal */
     889          24 :       vtr_dlist_ele_push_tail( eqvoc->vtr_dlist, vtr, eqvoc->vtr_pool );
     890          24 :     }
     891          60 :   }
     892             : 
     893             :   /* Pop and release marked voters until the first unmarked voter. */
     894             : 
     895          42 :   while( FD_LIKELY( !vtr_dlist_is_empty( eqvoc->vtr_dlist, eqvoc->vtr_pool ) ) ) {
     896          27 :     vtr_t * vtr = vtr_dlist_ele_pop_head( eqvoc->vtr_dlist, eqvoc->vtr_pool );
     897          27 :     if( FD_UNLIKELY( !vtr->next ) ) { /* can short-circuit since all the existing and new voters were appended */
     898          12 :       vtr_dlist_ele_push_tail( eqvoc->vtr_dlist, vtr, eqvoc->vtr_pool );
     899          12 :       break;
     900          12 :     }
     901          33 :     while( FD_LIKELY( !prf_dlist_is_empty( vtr->prf_dlist, eqvoc->prf_pool ) ) ) {
     902          18 :       prf_t * prf = prf_dlist_ele_pop_head( vtr->prf_dlist, eqvoc->prf_pool );
     903          18 :       prf_map_ele_remove_fast( eqvoc->prf_map, prf, eqvoc->prf_pool );
     904          18 :       prf_pool_ele_release( eqvoc->prf_pool, prf );
     905          18 :     }
     906          15 :     vtr_map_ele_remove_fast( eqvoc->vtr_map, vtr, eqvoc->vtr_pool );
     907          15 :     vtr_pool_ele_release( eqvoc->vtr_pool, vtr );
     908          15 :   }
     909             : 
     910             :   /* Second pass: acquire and insert new voters. */
     911             : 
     912          87 :   for( ulong i=0UL; i<cnt; i++ ) {
     913          60 :     fd_pubkey_t const * id = &id_keys[i];
     914          60 :     if( FD_LIKELY( vtr_map_ele_query( eqvoc->vtr_map, id, NULL, eqvoc->vtr_pool ) ) ) continue;
     915          36 :     vtr_t * vtr        = vtr_pool_ele_acquire( eqvoc->vtr_pool );
     916          36 :     vtr->from          = *id;
     917          36 :     vtr->prf_dlist_cnt = 0;
     918          36 :     vtr->next          = 0;
     919          36 :     vtr_map_ele_insert( eqvoc->vtr_map, vtr, eqvoc->vtr_pool );
     920          36 :     vtr_dlist_ele_push_tail( eqvoc->vtr_dlist, vtr, eqvoc->vtr_pool );
     921          36 :   }
     922          27 : }

Generated by: LCOV version 1.14