LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_bank.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 579 835 69.3 %
Date: 2025-09-19 04:41:14 Functions: 47 218 21.6 %

          Line data    Source code
       1             : #include "fd_bank.h"
       2             : #include "fd_runtime_const.h"
       3             : #include "sysvar/fd_sysvar_epoch_schedule.h"
       4             : 
       5             : ulong
       6          36 : fd_bank_align( void ) {
       7          36 :   return alignof(fd_bank_t);
       8          36 : }
       9             : 
      10             : ulong
      11           9 : fd_bank_footprint( void ) {
      12           9 :   ulong l = FD_LAYOUT_INIT;
      13           9 :   l = FD_LAYOUT_APPEND( l, fd_bank_align(), sizeof(fd_bank_t) );
      14           9 :   return FD_LAYOUT_FINI( l, fd_bank_align() );
      15           9 : }
      16             : 
      17             : /* Bank accesssors */
      18             : 
      19             : #define HAS_COW_1(type, name, footprint, align, has_lock)                                                          \
      20             :   type const *                                                                                                     \
      21          27 :   fd_bank_##name##_locking_query( fd_bank_t * bank ) {                                                             \
      22          27 :     fd_rwlock_read( &bank->name##_lock );                                                                          \
      23          27 :     /* If the pool element hasn't been setup yet, then return NULL */                                              \
      24          27 :     fd_bank_##name##_t * name##_pool = fd_bank_get_##name##_pool( bank );                                          \
      25          27 :     if( FD_UNLIKELY( name##_pool==NULL ) ) {                                                                       \
      26           0 :       FD_LOG_CRIT(( "NULL " #name " pool" ));                                                                      \
      27           0 :     }                                                                                                              \
      28          27 :     if( bank->name##_pool_idx==fd_bank_##name##_pool_idx_null( name##_pool ) ) {                                   \
      29           6 :       return NULL;                                                                                                 \
      30           6 :     }                                                                                                              \
      31          27 :     fd_bank_##name##_t * bank_##name = fd_bank_##name##_pool_ele( name##_pool, bank->name##_pool_idx );            \
      32          21 :     return (type *)bank_##name->data;                                                                              \
      33          27 :   }                                                                                                                \
      34             :   void                                                                                                             \
      35          27 :   fd_bank_##name##_end_locking_query( fd_bank_t * bank ) {                                                         \
      36          27 :     fd_rwlock_unread( &bank->name##_lock );                                                                        \
      37          27 :   }                                                                                                                \
      38             :   type *                                                                                                           \
      39          21 :   fd_bank_##name##_locking_modify( fd_bank_t * bank ) {                                                            \
      40          21 :     fd_rwlock_write( &bank->name##_lock );                                                                         \
      41          21 :     /* If the dirty flag is set, then we already have a pool element */                                            \
      42          21 :     /* that was copied over for the current bank. We can simply just */                                            \
      43          21 :     /* query the pool element and return it. */                                                                    \
      44          21 :     fd_bank_##name##_t * name##_pool = fd_bank_get_##name##_pool( bank );                                          \
      45          21 :     if( FD_UNLIKELY( name##_pool==NULL ) ) {                                                                       \
      46           0 :       FD_LOG_CRIT(( "NULL " #name " pool" ));                                                                      \
      47           0 :     }                                                                                                              \
      48          21 :     if( bank->name##_dirty ) {                                                                                     \
      49           3 :       fd_bank_##name##_t * bank_##name = fd_bank_##name##_pool_ele( name##_pool, bank->name##_pool_idx );          \
      50           3 :       return (type *)bank_##name->data;                                                                            \
      51           3 :     }                                                                                                              \
      52          21 :     if( FD_UNLIKELY( !fd_bank_##name##_pool_free( name##_pool ) ) ) {                                              \
      53           0 :       FD_LOG_CRIT(( "Failed to acquire " #name " pool element: pool is full" ));                                   \
      54           0 :     }                                                                                                              \
      55          18 :     fd_bank_##name##_t * child_##name = fd_bank_##name##_pool_ele_acquire( name##_pool );                          \
      56          18 :     if( FD_UNLIKELY( !child_##name ) ) {                                                                           \
      57           0 :       FD_LOG_CRIT(( "Failed to acquire " #name " pool element" ));                                                 \
      58           0 :     }                                                                                                              \
      59          18 :     /* If the dirty flag has not been set yet, we need to allocated a */                                           \
      60          18 :     /* new pool element and copy over the data from the parent idx.   */                                           \
      61          18 :     /* We also need to mark the dirty flag. */                                                                     \
      62          18 :     ulong child_idx = fd_bank_##name##_pool_idx( name##_pool, child_##name );                                      \
      63          18 :     if( bank->name##_pool_idx!=fd_bank_##name##_pool_idx_null( name##_pool ) ) {                                   \
      64           6 :       fd_bank_##name##_t * parent_##name = fd_bank_##name##_pool_ele( name##_pool, bank->name##_pool_idx );        \
      65           6 :       fd_memcpy( child_##name->data, parent_##name->data, fd_bank_##name##_footprint );                            \
      66           6 :     }                                                                                                              \
      67          18 :     bank->name##_pool_idx = child_idx;                                                                             \
      68          18 :     bank->name##_dirty    = 1;                                                                                     \
      69          18 :     return (type *)child_##name->data;                                                                             \
      70          18 :   }                                                                                                                \
      71             :   void                                                                                                             \
      72          21 :   fd_bank_##name##_end_locking_modify( fd_bank_t * bank ) {                                                        \
      73          21 :     fd_rwlock_unwrite( &bank->name##_lock );                                                                       \
      74          21 :   }
      75             : 
      76             : 
      77             : #define HAS_LOCK_0(type, name) \
      78             :   type const *                                             \
      79        1491 :   fd_bank_##name##_query( fd_bank_t const * bank ) {       \
      80        1491 :     return (type const *)fd_type_pun_const( bank->name );  \
      81        1491 :   }                                                        \
      82             :   type *                                                   \
      83         912 :   fd_bank_##name##_modify( fd_bank_t * bank ) {            \
      84         912 :     return (type *)fd_type_pun( bank->name );              \
      85         912 :   }
      86             : 
      87             : #define HAS_LOCK_1(type, name)                              \
      88             :   type const *                                              \
      89           0 :   fd_bank_##name##_locking_query( fd_bank_t * bank ) {      \
      90           0 :     fd_rwlock_read( &bank->name##_lock );                   \
      91           0 :     return (type const *)fd_type_pun_const( bank->name );   \
      92           0 :   }                                                         \
      93             :   type *                                                    \
      94         462 :   fd_bank_##name##_locking_modify( fd_bank_t * bank ) {     \
      95         462 :     fd_rwlock_write( &bank->name##_lock );                  \
      96         462 :     return (type *)fd_type_pun( bank->name );               \
      97         462 :   }                                                         \
      98             :   void                                                      \
      99           0 :   fd_bank_##name##_end_locking_query( fd_bank_t * bank ) {  \
     100           0 :     fd_rwlock_unread( &bank->name##_lock );                 \
     101           0 :   }                                                         \
     102             :   void                                                      \
     103         462 :   fd_bank_##name##_end_locking_modify( fd_bank_t * bank ) { \
     104         462 :     fd_rwlock_unwrite( &bank->name##_lock );                \
     105         462 :   }
     106             : 
     107             : #define HAS_COW_0(type, name, footprint, align, has_lock)   \
     108             :   HAS_LOCK_##has_lock(type, name)                           \
     109             :   void                                                      \
     110         939 :   fd_bank_##name##_set( fd_bank_t * bank, type value ) {    \
     111         939 :     FD_STORE( type, bank->name, value );                    \
     112         939 :   }                                                         \
     113             :   type                                                      \
     114         522 :   fd_bank_##name##_get( fd_bank_t const * bank ) {          \
     115         522 :     type val = FD_LOAD( type, bank->name );                 \
     116         522 :     return val;                                             \
     117         522 :   }
     118             : 
     119             : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     120             :   HAS_COW_##cow(type, name, footprint, align, has_lock)
     121           6 : FD_BANKS_ITER(X)
     122           6 : #undef X
     123           6 : #undef HAS_COW_0
     124           6 : #undef HAS_COW_1
     125           6 : #undef HAS_LOCK_0
     126           6 : #undef HAS_LOCK_1
     127           6 : 
     128           6 : /**********************************************************************/
     129           6 : 
     130           6 : ulong
     131         192 : fd_banks_align( void ) {
     132             :   /* TODO: The magic number here can probably be removed. */
     133         192 :   return 128UL;
     134         192 : }
     135             : 
     136             : ulong
     137          18 : fd_banks_footprint( ulong max_total_banks, ulong FD_PARAM_UNUSED max_fork_width ) {
     138             : 
     139             :   /* max_fork_width is used in the macro below. */
     140             : 
     141          18 :   ulong map_chain_cnt = fd_ulong_pow2_up( max_total_banks );
     142             : 
     143          18 :   ulong l = FD_LAYOUT_INIT;
     144          18 :   l = FD_LAYOUT_APPEND( l, fd_banks_align(),      sizeof(fd_banks_t) );
     145          18 :   l = FD_LAYOUT_APPEND( l, fd_banks_pool_align(), fd_banks_pool_footprint( max_total_banks ) );
     146          18 :   l = FD_LAYOUT_APPEND( l, fd_banks_map_align(),  fd_banks_map_footprint( map_chain_cnt ) );
     147             : 
     148             :   /* Need to count the footprint for all of the CoW pools. The footprint
     149             :      on each CoW pool depends on if the field limits the fork width. */
     150             : 
     151          18 :   #define HAS_COW_1_LIMIT_1(name) \
     152          72 :     l = FD_LAYOUT_APPEND( l, fd_bank_##name##_pool_align(), fd_bank_##name##_pool_footprint( max_fork_width ) );
     153             : 
     154          18 :   #define HAS_COW_1_LIMIT_0(name) \
     155          18 :     l = FD_LAYOUT_APPEND( l, fd_bank_##name##_pool_align(), fd_bank_##name##_pool_footprint( max_total_banks ) );
     156             : 
     157             :   /* Do nothing for these. */
     158          18 :   #define HAS_COW_0_LIMIT_0(name)
     159             : 
     160          18 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock)  \
     161          90 :     HAS_COW_##cow##_LIMIT_##limit_fork_width(name)
     162          90 :   FD_BANKS_ITER(X)
     163          18 :   #undef X
     164          18 :   #undef HAS_COW_0_LIMIT_0
     165          18 :   #undef HAS_COW_1_LIMIT_0
     166          18 :   #undef HAS_COW_1_LIMIT_1
     167             : 
     168          18 :   return FD_LAYOUT_FINI( l, fd_banks_align() );
     169          18 : }
     170             : 
     171             : void *
     172           9 : fd_banks_new( void * shmem, ulong max_total_banks, ulong max_fork_width ) {
     173             : 
     174           9 :   fd_banks_t * banks = (fd_banks_t *)shmem;
     175             : 
     176           9 :   if( FD_UNLIKELY( !banks ) ) {
     177           0 :     FD_LOG_WARNING(( "NULL banks" ));
     178           0 :     return NULL;
     179           0 :   }
     180             : 
     181           9 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)banks, fd_banks_align() ) ) ) {
     182           0 :     FD_LOG_WARNING(( "misaligned banks" ));
     183           0 :     return NULL;
     184           0 :   }
     185             : 
     186             :   /* Set the rwlock to unlocked. */
     187           9 :   fd_rwlock_unwrite( &banks->rwlock );
     188             : 
     189           9 :   ulong map_chain_cnt = fd_ulong_pow2_up( max_total_banks );
     190             : 
     191             :   /* First, layout the banks and the pool/map used by fd_banks_t. */
     192           9 :   FD_SCRATCH_ALLOC_INIT( l, banks );
     193           9 :   banks           = FD_SCRATCH_ALLOC_APPEND( l, fd_banks_align(),      sizeof(fd_banks_t) );
     194           9 :   void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_banks_pool_align(), fd_banks_pool_footprint( max_total_banks ) );
     195           9 :   void * map_mem  = FD_SCRATCH_ALLOC_APPEND( l, fd_banks_map_align(),  fd_banks_map_footprint( map_chain_cnt ) );
     196             : 
     197             :   /* Need to layout all of the CoW pools. */
     198           0 :   #define HAS_COW_1_LIMIT_1(name) \
     199          36 :     void * name##_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_bank_##name##_pool_align(), fd_bank_##name##_pool_footprint( max_fork_width ) );
     200             : 
     201           0 :   #define HAS_COW_1_LIMIT_0(name) \
     202           9 :     void * name##_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_bank_##name##_pool_align(), fd_bank_##name##_pool_footprint( max_total_banks ) );
     203             : 
     204             :   /* Do nothing for these. */
     205           0 :   #define HAS_COW_0_LIMIT_0(name)
     206             : 
     207           0 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     208          45 :     HAS_COW_##cow##_LIMIT_##limit_fork_width(name)
     209          45 :   FD_BANKS_ITER(X)
     210           0 :   #undef X
     211           0 :   #undef HAS_COW_0_LIMIT_0
     212           0 :   #undef HAS_COW_1_LIMIT_0
     213           0 :   #undef HAS_COW_1_LIMIT_1
     214             : 
     215           9 :   if( FD_UNLIKELY( FD_SCRATCH_ALLOC_FINI( l, fd_banks_align() ) != (ulong)banks + fd_banks_footprint( max_total_banks, max_fork_width ) ) ) {
     216           0 :     FD_LOG_WARNING(( "fd_banks_new: bad layout" ));
     217           0 :     return NULL;
     218           0 :   }
     219             : 
     220           9 :   void * pool = fd_banks_pool_new( pool_mem, max_total_banks );
     221           9 :   if( FD_UNLIKELY( !pool ) ) {
     222           0 :     FD_LOG_WARNING(( "Failed to create bank pool" ));
     223           0 :     return NULL;
     224           0 :   }
     225             : 
     226           9 :   fd_bank_t * bank_pool = fd_banks_pool_join( pool );
     227           9 :   if( FD_UNLIKELY( !bank_pool ) ) {
     228           0 :     FD_LOG_WARNING(( "Failed to join bank pool" ));
     229           0 :     return NULL;
     230           0 :   }
     231             : 
     232           9 :   fd_banks_set_bank_pool( banks, bank_pool );
     233             : 
     234           9 :   void * map = fd_banks_map_new( map_mem, map_chain_cnt, 999UL );
     235           9 :   if( FD_UNLIKELY( !map ) ) {
     236           0 :     FD_LOG_WARNING(( "Failed to create bank map" ));
     237           0 :     return NULL;
     238           0 :   }
     239             : 
     240           9 :   fd_banks_map_t * bank_map = fd_banks_map_join( map_mem );
     241           9 :   if( FD_UNLIKELY( !bank_map ) ) {
     242           0 :     FD_LOG_WARNING(( "Failed to join bank map" ));
     243           0 :     return NULL;
     244           0 :   }
     245             : 
     246           9 :   fd_banks_set_bank_map( banks, bank_map );
     247             : 
     248             :   /* Now, call _new() and _join() for all of the CoW pools. */
     249           9 :   #define HAS_COW_1_LIMIT_1(name)                                                     \
     250          36 :     void * name##_mem = fd_bank_##name##_pool_new( name##_pool_mem, max_fork_width ); \
     251          36 :     if( FD_UNLIKELY( !name##_mem ) ) {                                                \
     252           0 :       FD_LOG_WARNING(( "Failed to create " #name " pool" ));                          \
     253           0 :       return NULL;                                                                    \
     254           0 :     }                                                                                 \
     255          36 :     fd_bank_##name##_t * name##_pool = fd_bank_##name##_pool_join( name##_pool_mem ); \
     256          36 :     if( FD_UNLIKELY( !name##_pool ) ) {                                               \
     257           0 :       FD_LOG_WARNING(( "Failed to join " #name " pool" ));                            \
     258           0 :       return NULL;                                                                    \
     259           0 :     }                                                                                 \
     260          36 :     fd_banks_set_##name##_pool( banks, name##_pool );
     261             : 
     262           9 :   #define HAS_COW_1_LIMIT_0(name)                                                      \
     263           9 :     void * name##_mem = fd_bank_##name##_pool_new( name##_pool_mem, max_total_banks ); \
     264           9 :     if( FD_UNLIKELY( !name##_mem ) ) {                                                 \
     265           0 :       FD_LOG_WARNING(( "Failed to create " #name " pool" ));                           \
     266           0 :       return NULL;                                                                     \
     267           0 :     }                                                                                  \
     268           9 :     fd_bank_##name##_t * name##_pool = fd_bank_##name##_pool_join( name##_pool_mem );  \
     269           9 :     if( FD_UNLIKELY( !name##_pool ) ) {                                                \
     270           0 :       FD_LOG_WARNING(( "Failed to join " #name " pool" ));                             \
     271           0 :       return NULL;                                                                     \
     272           0 :     }                                                                                  \
     273           9 :     fd_banks_set_##name##_pool( banks, name##_pool );
     274             : 
     275             :   /* Do nothing for these. */
     276           9 :   #define HAS_COW_0_LIMIT_0(name)
     277             : 
     278           9 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     279          45 :     HAS_COW_##cow##_LIMIT_##limit_fork_width(name)
     280          90 :   FD_BANKS_ITER(X)
     281          90 :   #undef X
     282          90 :   #undef HAS_COW_0_LIMIT_0
     283          90 :   #undef HAS_COW_1_LIMIT_1
     284          90 :   #undef HAS_COW_1_LIMIT_0
     285             : 
     286          90 :   banks->max_total_banks = max_total_banks;
     287          90 :   banks->max_fork_width  = max_fork_width;
     288          90 :   banks->root_idx        = ULONG_MAX;
     289             : 
     290          90 :   if( FD_UNLIKELY( !fd_stake_delegations_new( banks->stake_delegations_root, FD_RUNTIME_MAX_STAKE_ACCOUNTS, 0 ) ) ) {
     291           0 :     FD_LOG_WARNING(( "Unable to create stake delegations root" ));
     292           0 :     return NULL;
     293           0 :   }
     294             : 
     295           9 :   FD_COMPILER_MFENCE();
     296           9 :   FD_VOLATILE( banks->magic ) = FD_BANKS_MAGIC;
     297           9 :   FD_COMPILER_MFENCE();
     298             : 
     299           9 :   return shmem;
     300          90 : }
     301             : 
     302             : fd_banks_t *
     303          15 : fd_banks_join( void * mem ) {
     304          15 :   fd_banks_t * banks = (fd_banks_t *)mem;
     305             : 
     306          15 :   if( FD_UNLIKELY( !banks ) ) {
     307           0 :     FD_LOG_WARNING(( "NULL banks" ));
     308           0 :     return NULL;
     309           0 :   }
     310             : 
     311          15 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)banks, fd_banks_align() ) ) ) {
     312           0 :     FD_LOG_WARNING(( "misaligned banks" ));
     313           0 :     return NULL;
     314           0 :   }
     315             : 
     316          15 :   if( FD_UNLIKELY( banks->magic!=FD_BANKS_MAGIC ) ) {
     317           3 :     FD_LOG_WARNING(( "Invalid banks magic" ));
     318           3 :     return NULL;
     319           3 :   }
     320             : 
     321          12 :   ulong map_chain_cnt = fd_ulong_pow2_up( banks->max_total_banks );
     322             : 
     323          12 :   FD_SCRATCH_ALLOC_INIT( l, banks );
     324          12 :   banks           = FD_SCRATCH_ALLOC_APPEND( l, fd_banks_align(),      sizeof(fd_banks_t) );
     325          12 :   void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_banks_pool_align(), fd_banks_pool_footprint( banks->max_total_banks ) );
     326          12 :   void * map_mem  = FD_SCRATCH_ALLOC_APPEND( l, fd_banks_map_align(),  fd_banks_map_footprint( map_chain_cnt ) );
     327             : 
     328             :   /* Need to layout all of the CoW pools. */
     329           0 :   #define HAS_COW_1_LIMIT_1(name) \
     330          48 :     void * name##_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_bank_##name##_pool_align(), fd_bank_##name##_pool_footprint( banks->max_fork_width ) );
     331             : 
     332           0 :   #define HAS_COW_1_LIMIT_0(name) \
     333          12 :     void * name##_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_bank_##name##_pool_align(), fd_bank_##name##_pool_footprint( banks->max_total_banks ) );
     334             : 
     335             :   /* Don't need to layout if not CoW. */
     336           0 :   #define HAS_COW_0_LIMIT_0(name)
     337             : 
     338           0 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     339          60 :     HAS_COW_##cow##_LIMIT_##limit_fork_width(name)
     340          60 :   FD_BANKS_ITER(X)
     341           0 :   #undef X
     342           0 :   #undef HAS_COW_0_LIMIT_0
     343           0 :   #undef HAS_COW_1_LIMIT_0
     344           0 :   #undef HAS_COW_1_LIMIT_1
     345             : 
     346          12 :   FD_SCRATCH_ALLOC_FINI( l, fd_banks_align() );
     347             : 
     348          60 :   fd_bank_t * banks_pool = fd_banks_get_bank_pool( banks );
     349          60 :   if( FD_UNLIKELY( !banks_pool ) ) {
     350           0 :     FD_LOG_WARNING(( "Failed to join bank pool" ));
     351           0 :     return NULL;
     352           0 :   }
     353             : 
     354          12 :   if( FD_UNLIKELY( banks_pool!=fd_banks_pool_join( pool_mem ) ) ) {
     355           0 :     FD_LOG_WARNING(( "Failed to join bank pool" ));
     356           0 :     return NULL;
     357           0 :   }
     358             : 
     359          12 :   fd_banks_map_t * bank_map = fd_banks_get_bank_map( banks );
     360          12 :   if( FD_UNLIKELY( !bank_map ) ) {
     361           0 :     FD_LOG_WARNING(( "Failed to join bank map" ));
     362           0 :     return NULL;
     363           0 :   }
     364             : 
     365          12 :   if( FD_UNLIKELY( bank_map!=fd_banks_map_join( map_mem ) ) ) {
     366           0 :     FD_LOG_WARNING(( "Failed to join bank map" ));
     367           0 :     return NULL;
     368           0 :   }
     369             : 
     370             :   /* Now, call _join() for all of the CoW pools. */
     371          12 :   #define HAS_COW_1(name)                                                             \
     372          60 :     fd_bank_##name##_t * name##_pool = fd_banks_get_##name##_pool( banks );           \
     373          60 :     if( FD_UNLIKELY( !name##_pool ) ) {                                               \
     374           0 :       FD_LOG_WARNING(( "Failed to join " #name " pool" ));                            \
     375           0 :       return NULL;                                                                    \
     376           0 :     }                                                                                 \
     377          60 :     if( FD_UNLIKELY( name##_pool!=fd_bank_##name##_pool_join( name##_pool_mem ) ) ) { \
     378           0 :       FD_LOG_WARNING(( "Failed to join " #name " pool" ));                            \
     379           0 :       return NULL;                                                                    \
     380           0 :     }
     381             : 
     382             :   /* Do nothing when the field is not CoW. */
     383          12 :   #define HAS_COW_0(name)
     384             : 
     385          12 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     386          60 :     HAS_COW_##cow(name)
     387         120 :   FD_BANKS_ITER(X)
     388          12 :   #undef X
     389          12 :   #undef HAS_COW_0
     390          12 :   #undef HAS_COW_1
     391             : 
     392             : 
     393          12 :   return banks;
     394         120 : }
     395             : 
     396             : void *
     397           9 : fd_banks_leave( fd_banks_t * banks ) {
     398             : 
     399           9 :   if( FD_UNLIKELY( !banks ) ) {
     400           0 :     FD_LOG_WARNING(( "NULL banks" ));
     401           0 :     return NULL;
     402           0 :   }
     403             : 
     404           9 :   return (void *)banks;
     405           9 : }
     406             : 
     407             : void *
     408           3 : fd_banks_delete( void * shmem ) {
     409             : 
     410           3 :   if( FD_UNLIKELY( !shmem ) ) {
     411           0 :     FD_LOG_WARNING(( "NULL banks" ));
     412           0 :     return NULL;
     413           0 :   }
     414             : 
     415           3 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)shmem, fd_banks_align() ) ) ) {
     416           0 :     FD_LOG_WARNING(( "misaligned banks" ));
     417           0 :     return NULL;
     418           0 :   }
     419             : 
     420           3 :   fd_banks_t * banks = (fd_banks_t *)shmem;
     421           3 :   banks->magic = 0UL;
     422             : 
     423           3 :   return shmem;
     424           3 : }
     425             : 
     426             : fd_bank_t *
     427             : fd_banks_init_bank( fd_banks_t * banks,
     428           9 :                     fd_eslot_t   eslot ) {
     429             : 
     430           9 :   if( FD_UNLIKELY( !banks ) ) {
     431           0 :     FD_LOG_WARNING(( "NULL banks" ));
     432           0 :     return NULL;
     433           0 :   }
     434             : 
     435           9 :   fd_bank_t *      bank_pool = fd_banks_get_bank_pool( banks );
     436           9 :   fd_banks_map_t * bank_map  = fd_banks_get_bank_map( banks );
     437             : 
     438           9 :   fd_rwlock_write( &banks->rwlock );
     439             : 
     440           9 :   fd_bank_t * bank = fd_banks_pool_ele_acquire( bank_pool );
     441           9 :   if( FD_UNLIKELY( bank==NULL ) ) {
     442           0 :     FD_LOG_WARNING(( "Failed to acquire bank" ));
     443           0 :     fd_rwlock_unwrite( &banks->rwlock );
     444           0 :     return NULL;
     445           0 :   }
     446             : 
     447           9 :   memset( bank, 0, fd_bank_footprint() );
     448             : 
     449           9 :   ulong      null_idx   = fd_banks_pool_idx_null( bank_pool );
     450           9 :   fd_eslot_t null_eslot = { .id = ULONG_MAX };
     451           9 :   bank->eslot_          = eslot;
     452           9 :   bank->parent_eslot_   = null_eslot;
     453           9 :   bank->next            = null_idx;
     454           9 :   bank->parent_idx      = null_idx;
     455           9 :   bank->child_idx       = null_idx;
     456           9 :   bank->sibling_idx     = null_idx;
     457             : 
     458             :   /* Set all CoW fields to null. */
     459           9 :   #define HAS_COW_1(name)                                                             \
     460          45 :     fd_bank_##name##_t * name##_pool = fd_banks_get_##name##_pool( banks );           \
     461          45 :     fd_bank_set_##name##_pool( bank, name##_pool );                                   \
     462          45 :     bank->name##_pool_idx            = fd_bank_##name##_pool_idx_null( name##_pool ); \
     463          45 :     bank->name##_dirty               = 0;
     464             : 
     465             :   /* Do nothing for these. */
     466           9 :   #define HAS_COW_0(name)
     467             : 
     468           9 :   #define HAS_LOCK_1(name) \
     469          63 :     fd_rwlock_unwrite(&bank->name##_lock);
     470           9 :   #define HAS_LOCK_0(name)
     471             : 
     472           9 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     473          45 :     HAS_COW_##cow(name);                                                   \
     474         423 :     HAS_LOCK_##has_lock(name)
     475         423 :   FD_BANKS_ITER(X)
     476           9 :   #undef X
     477           9 :   #undef HAS_COW_0
     478           9 :   #undef HAS_COW_1
     479           9 :   #undef HAS_LOCK_0
     480           9 :   #undef HAS_LOCK_1
     481             : 
     482           9 :   bank->flags  = FD_BANK_FLAGS_INIT;
     483           9 :   bank->refcnt = 0UL;
     484             : 
     485           9 :   fd_banks_map_ele_insert( bank_map, bank, bank_pool );
     486             : 
     487             :   /* Now that the node is inserted, update the root */
     488             : 
     489           9 :   banks->root_idx = fd_banks_pool_idx( bank_pool, bank );
     490             : 
     491           9 :   fd_rwlock_unwrite( &banks->rwlock );
     492           9 :   return bank;
     493           9 : }
     494             : 
     495             : fd_bank_t *
     496             : fd_banks_get_bank( fd_banks_t * banks,
     497         111 :                    fd_eslot_t   eslot ) {
     498         111 :   fd_bank_t *      bank_pool = fd_banks_get_bank_pool( banks );
     499         111 :   fd_banks_map_t * bank_map  = fd_banks_get_bank_map( banks );
     500             : 
     501         111 :   fd_rwlock_read( &banks->rwlock );
     502         111 :   ulong idx = fd_banks_map_idx_query_const( bank_map, &eslot, ULONG_MAX, bank_pool );
     503         111 :   if( FD_UNLIKELY( idx==ULONG_MAX ) ) {
     504          42 :     FD_LOG_DEBUG(( "Failed to get bank idx for (slot %lu, prime %lu)", (ulong)eslot.slot, (ulong)eslot.prime ));
     505          42 :     fd_rwlock_unread( &banks->rwlock );
     506          42 :     return NULL;
     507          42 :   }
     508             : 
     509          69 :   fd_bank_t * bank = fd_banks_pool_ele( bank_pool, idx );
     510          69 :   if( FD_UNLIKELY( !bank ) ) {
     511           0 :     FD_LOG_WARNING(( "Failed to get bank for (slot %lu, prime %lu)", (ulong)eslot.slot, (ulong)eslot.prime ));
     512           0 :     fd_rwlock_unread( &banks->rwlock );
     513           0 :     return NULL;
     514           0 :   }
     515          69 :   fd_rwlock_unread( &banks->rwlock );
     516          69 :   return bank;
     517          69 : }
     518             : 
     519             : 
     520             : fd_bank_t *
     521             : fd_banks_clone_from_parent( fd_banks_t * banks,
     522             :                             fd_eslot_t   eslot,
     523          66 :                             fd_eslot_t   parent_eslot ) {
     524             : 
     525          66 :   fd_rwlock_write( &banks->rwlock );
     526             : 
     527          66 :   fd_bank_t * bank_pool = fd_banks_get_bank_pool( banks );
     528          66 :   if( FD_UNLIKELY( !bank_pool ) ) {
     529           0 :     FD_LOG_CRIT(( "invariant violation: failed to get bank pool" ));
     530           0 :   }
     531             : 
     532          66 :   fd_banks_map_t * bank_map = fd_banks_get_bank_map( banks );
     533          66 :   if( FD_UNLIKELY( !bank_map ) ) {
     534           0 :     FD_LOG_CRIT(( "invariant violation: failed to get bank map" ));
     535           0 :   }
     536             : 
     537             :   /* See if we already recovered the bank */
     538             : 
     539          66 :   fd_bank_t * old_bank = fd_banks_map_ele_query( bank_map, &eslot, NULL, bank_pool );
     540          66 :   if( FD_UNLIKELY( !!old_bank ) ) {
     541           0 :     FD_LOG_CRIT(( "Invariant violation: bank for (slot %lu, prime %lu) already exists", (ulong)eslot.slot, (ulong)eslot.prime ));
     542           0 :   }
     543             : 
     544             :   /* First query for the parent bank */
     545             : 
     546          66 :   fd_bank_t * parent_bank = fd_banks_map_ele_query( bank_map, &parent_eslot, NULL, bank_pool );
     547             : 
     548          66 :   if( FD_UNLIKELY( !parent_bank ) ) {
     549           0 :     FD_LOG_WARNING(( "Failed to get bank for parent (slot %lu, prime %lu)", (ulong)parent_eslot.slot, (ulong)parent_eslot.prime ));
     550           0 :     fd_rwlock_unwrite( &banks->rwlock );
     551           0 :     return NULL;
     552           0 :   }
     553             : 
     554          66 :   if( FD_UNLIKELY( fd_bank_eslot_get( parent_bank ).id != parent_eslot.id ) ) {
     555           0 :     FD_LOG_WARNING(( "Parent eslot mismatch (slot %lu, prime %lu) != (slot %lu, prime %lu)", (ulong)fd_bank_eslot_get( parent_bank ).slot, (ulong)fd_bank_eslot_get( parent_bank ).prime, (ulong)parent_eslot.slot, (ulong)parent_eslot.prime ));
     556           0 :     fd_rwlock_unwrite( &banks->rwlock );
     557           0 :     return NULL;
     558           0 :   }
     559             : 
     560          66 :   ulong parent_idx = fd_banks_pool_idx( bank_pool, parent_bank );
     561             : 
     562             :   /* Now acquire a new bank */
     563             : 
     564          66 :   FD_LOG_INFO(( "new bank (slot %lu, prime %lu), (parent slot %lu, prime %lu), fd_banks_pool_max: %lu, fd_banks_pool_free: %lu", (ulong)eslot.slot, (ulong)eslot.prime, (ulong)parent_eslot.slot, (ulong)parent_eslot.prime, fd_banks_pool_max( bank_pool ), fd_banks_pool_free( bank_pool ) ));
     565             : 
     566          66 :   if( FD_UNLIKELY( !fd_banks_pool_free( bank_pool ) ) ) {
     567           0 :     FD_LOG_WARNING(( "No free banks" ));
     568           0 :     fd_rwlock_unwrite( &banks->rwlock );
     569           0 :     return NULL;
     570           0 :   }
     571             : 
     572          66 :   fd_bank_t * new_bank = fd_banks_pool_ele_acquire( bank_pool );
     573          66 :   if( FD_UNLIKELY( !new_bank ) ) {
     574           0 :     FD_LOG_WARNING(( "Failed to acquire bank" ));
     575           0 :     fd_rwlock_unwrite( &banks->rwlock );
     576           0 :     return NULL;
     577           0 :   }
     578             : 
     579          66 :   ulong null_idx = fd_banks_pool_idx_null( bank_pool );
     580             : 
     581          66 :   new_bank->eslot_        = eslot;
     582          66 :   new_bank->parent_eslot_ = parent_eslot;
     583          66 :   new_bank->next          = null_idx;
     584          66 :   new_bank->parent_idx    = null_idx;
     585          66 :   new_bank->child_idx     = null_idx;
     586          66 :   new_bank->sibling_idx   = null_idx;
     587             : 
     588          66 :   fd_banks_map_ele_insert( bank_map, new_bank, bank_pool );
     589             : 
     590          66 :   ulong child_idx = fd_banks_pool_idx( bank_pool, new_bank );
     591             : 
     592             :   /* Link node->parent */
     593             : 
     594          66 :   new_bank->parent_idx = parent_idx;
     595             : 
     596             :   /* Link parent->node and sibling->node */
     597             : 
     598          66 :   if( FD_LIKELY( parent_bank->child_idx==null_idx ) ) {
     599             : 
     600             :     /* This is the first child so set as left-most child */
     601             : 
     602          36 :     parent_bank->child_idx = child_idx;
     603             : 
     604          36 :   } else {
     605             : 
     606             :     /* Already have children so iterate to right-most sibling. */
     607             : 
     608          30 :     fd_bank_t * curr_bank = fd_banks_pool_ele( bank_pool, parent_bank->child_idx );
     609          36 :     while( curr_bank->sibling_idx != null_idx ) curr_bank = fd_banks_pool_ele( bank_pool, curr_bank->sibling_idx );
     610             : 
     611             :     /* Link to right-most sibling. */
     612             : 
     613          30 :     curr_bank->sibling_idx = child_idx;
     614             : 
     615          30 :   }
     616             : 
     617             :   /* We want to copy over the fields from the parent to the child,
     618             :      except for the fields which correspond to the header of the bank
     619             :      struct which is used for pool and map management. We can take
     620             :      advantage of the fact that those fields are laid out at the top
     621             :      of the bank struct.
     622             : 
     623             :      TODO: We don't need to copy over the stake delegations delta. */
     624             : 
     625          66 :   memcpy( (uchar *)new_bank + FD_BANK_HEADER_SIZE, (uchar *)parent_bank + FD_BANK_HEADER_SIZE, sizeof(fd_bank_t) - FD_BANK_HEADER_SIZE );
     626             : 
     627             :   /* Setup all of the CoW fields. */
     628          66 :   #define HAS_COW_1(name)                                                   \
     629         330 :     new_bank->name##_pool_idx        = parent_bank->name##_pool_idx;        \
     630         330 :     new_bank->name##_dirty           = 0UL;                                 \
     631         330 :     fd_bank_##name##_t * name##_pool = fd_banks_get_##name##_pool( banks ); \
     632         330 :     fd_bank_set_##name##_pool( new_bank, name##_pool );
     633             : 
     634             :   /* Do nothing if not CoW. */
     635          66 :   #define HAS_COW_0(name)
     636             : 
     637             :   /* Setup locks for new bank as free. */
     638          66 :   #define HAS_LOCK_1(name) \
     639         462 :     fd_rwlock_unwrite(&new_bank->name##_lock);
     640          66 :   #define HAS_LOCK_0(name)
     641             : 
     642          66 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     643         330 :     HAS_COW_##cow(name);                                 \
     644        3102 :     HAS_LOCK_##has_lock(name)
     645        3102 :   FD_BANKS_ITER(X)
     646          66 :   #undef X
     647          66 :   #undef HAS_COW_0
     648          66 :   #undef HAS_COW_1
     649          66 :   #undef HAS_LOCK_0
     650          66 :   #undef HAS_LOCK_1
     651             : 
     652          66 :   new_bank->flags = FD_BANK_FLAGS_INIT;
     653             :   /* If the parent bank is dead, then we also need to mark the child
     654             :      bank as being a dead block. */
     655          66 :   if( FD_UNLIKELY( parent_bank->flags & FD_BANK_FLAGS_DEAD ) ) {
     656           0 :     new_bank->flags |= FD_BANK_FLAGS_DEAD;
     657           0 :   }
     658             : 
     659          66 :   new_bank->refcnt = 0UL;
     660             : 
     661             :   /* Delta field does not need to be copied over. The dirty flag just
     662             :      needs to be cleared if it was set. */
     663          66 :   new_bank->stake_delegations_delta_dirty = 0;
     664          66 :   fd_rwlock_unwrite( &new_bank->stake_delegations_delta_lock );
     665             : 
     666          66 :   fd_rwlock_unwrite( &banks->rwlock );
     667             : 
     668          66 :   return new_bank;
     669          66 : }
     670             : 
     671             : /* Apply a fd_stake_delegations_t into the root. This assumes that there
     672             :    are no in-between, un-applied banks between the root and the bank
     673             :    being applied. This also assumes that the stake delegation object
     674             :    that is being applied is a delta. */
     675             : 
     676             : static inline void
     677             : fd_banks_stake_delegations_apply_delta( fd_bank_t *              bank,
     678          93 :                                         fd_stake_delegations_t * stake_delegations_base ) {
     679             : 
     680          93 :   if( !bank->stake_delegations_delta_dirty ) {
     681          36 :     return;
     682          36 :   }
     683             : 
     684          57 :   fd_stake_delegations_t * stake_delegations_delta = fd_stake_delegations_join( bank->stake_delegations_delta );
     685          57 :   if( FD_UNLIKELY( !stake_delegations_delta ) ) {
     686           0 :     FD_LOG_CRIT(( "Failed to join stake delegations delta" ));
     687           0 :   }
     688             : 
     689          57 :   fd_stake_delegations_iter_t iter_[1];
     690          57 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations_delta );
     691         141 :        !fd_stake_delegations_iter_done( iter );
     692          84 :        fd_stake_delegations_iter_next( iter ) ) {
     693          84 :     fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     694          84 :     if( FD_LIKELY( !stake_delegation->is_tombstone ) ) {
     695          72 :       fd_stake_delegations_update(
     696          72 :           stake_delegations_base,
     697          72 :           &stake_delegation->stake_account,
     698          72 :           &stake_delegation->vote_account,
     699          72 :           stake_delegation->stake,
     700          72 :           stake_delegation->activation_epoch,
     701          72 :           stake_delegation->deactivation_epoch,
     702          72 :           stake_delegation->credits_observed,
     703          72 :           stake_delegation->warmup_cooldown_rate
     704          72 :       );
     705          72 :     } else {
     706          12 :       fd_stake_delegations_remove( stake_delegations_base, &stake_delegation->stake_account );
     707          12 :     }
     708          84 :   }
     709          57 : }
     710             : 
     711             : /* fd_bank_stake_delegation_apply_deltas applies all of the stake
     712             :    delegations for the entire direct ancestry from the bank to the
     713             :    root into a full fd_stake_delegations_t object. */
     714             : 
     715             : static inline void
     716             : fd_bank_stake_delegation_apply_deltas( fd_banks_t *             banks,
     717             :                                        fd_bank_t *              bank,
     718          33 :                                        fd_stake_delegations_t * stake_delegations ) {
     719             : 
     720             :   /* Naively what we want to do is iterate from the old root to the new
     721             :      root and apply the delta to the full state iteratively. */
     722             : 
     723             :   /* First, gather all of the pool indicies that we want to apply deltas
     724             :      for in reverse order starting from the new root. We want to exclude
     725             :      the old root since its delta has been applied previously. */
     726          33 :   ulong pool_indicies[ banks->max_total_banks ];
     727          33 :   ulong pool_indicies_len = 0UL;
     728             : 
     729          33 :   fd_bank_t * bank_pool = fd_banks_get_bank_pool( banks );
     730             : 
     731          33 :   ulong curr_idx = fd_banks_pool_idx( bank_pool, bank );
     732         126 :   while( curr_idx!=fd_banks_pool_idx_null( bank_pool ) ) {
     733          93 :     pool_indicies[pool_indicies_len++] = curr_idx;
     734          93 :     fd_bank_t * curr_bank = fd_banks_pool_ele( bank_pool, curr_idx );
     735          93 :     curr_idx = curr_bank->parent_idx;
     736          93 :   }
     737             : 
     738             :   /* We have populated all of the indicies that we need to apply deltas
     739             :      from in reverse order. */
     740             : 
     741         126 :   for( ulong i=pool_indicies_len; i>0; i-- ) {
     742          93 :     ulong idx = pool_indicies[i-1UL];
     743          93 :     fd_banks_stake_delegations_apply_delta( fd_banks_pool_ele( bank_pool, idx ), stake_delegations );
     744          93 :   }
     745          33 : }
     746             : 
     747             : fd_stake_delegations_t *
     748          24 : fd_bank_stake_delegations_frontier_query( fd_banks_t * banks, fd_bank_t * bank ) {
     749             : 
     750          24 :   fd_rwlock_write( &banks->rwlock );
     751             : 
     752             :   /* First copy the rooted state into the frontier. */
     753          24 :   memcpy( banks->stake_delegations_frontier, banks->stake_delegations_root, FD_STAKE_DELEGATIONS_FOOTPRINT );
     754             : 
     755             :   /* Now apply all of the updates from the bank and all of its
     756             :      ancestors in order to the frontier. */
     757          24 :   fd_stake_delegations_t * stake_delegations = fd_stake_delegations_join( banks->stake_delegations_frontier );
     758          24 :   fd_bank_stake_delegation_apply_deltas( banks, bank, stake_delegations );
     759             : 
     760          24 :   fd_rwlock_unwrite( &banks->rwlock );
     761             : 
     762          24 :   return stake_delegations;
     763          24 : }
     764             : 
     765             : fd_stake_delegations_t *
     766           3 : fd_banks_stake_delegations_root_query( fd_banks_t * banks ) {
     767           3 :   return fd_stake_delegations_join( banks->stake_delegations_root );
     768           3 : }
     769             : 
     770             : fd_bank_t const *
     771             : fd_banks_publish( fd_banks_t * banks,
     772           9 :                   fd_eslot_t   eslot ) {
     773             : 
     774           9 :   fd_rwlock_write( &banks->rwlock );
     775             : 
     776           9 :   fd_bank_t *      bank_pool = fd_banks_get_bank_pool( banks );
     777           9 :   fd_banks_map_t * bank_map  = fd_banks_get_bank_map( banks );
     778             : 
     779           9 :   ulong null_idx = fd_banks_pool_idx_null( bank_pool );
     780             : 
     781             :   /* We want to replace the old root with the new root. This means we
     782             :      have to remove banks that aren't descendants of the new root. */
     783             : 
     784           9 :   fd_bank_t const * old_root = fd_banks_root( banks );
     785           9 :   if( FD_UNLIKELY( !old_root ) ) {
     786           0 :     FD_LOG_WARNING(( "Failed to get root bank" ));
     787           0 :     fd_rwlock_unwrite( &banks->rwlock );
     788           0 :     return NULL;
     789           0 :   }
     790             : 
     791           9 :   if( FD_UNLIKELY( old_root->refcnt!=0UL ) ) {
     792           0 :     FD_LOG_CRIT(( "refcnt for old root bank (slot %lu, prime %lu) is %lu", (ulong)old_root->eslot_.slot, (ulong)old_root->eslot_.prime, old_root->refcnt ));
     793           0 :   }
     794             : 
     795           9 :   fd_bank_t * new_root = fd_banks_map_ele_query( bank_map, &eslot, NULL, bank_pool );
     796           9 :   if( FD_UNLIKELY( !new_root ) ) {
     797           0 :     FD_LOG_WARNING(( "Failed to get new root bank" ));
     798           0 :     fd_rwlock_unwrite( &banks->rwlock );
     799           0 :     return NULL;
     800           0 :   }
     801             : 
     802           9 :   fd_stake_delegations_t * stake_delegations = fd_stake_delegations_join( banks->stake_delegations_root );
     803           9 :   fd_bank_stake_delegation_apply_deltas( banks, new_root, stake_delegations );
     804           9 :   new_root->stake_delegations_delta_dirty = 0;
     805             : 
     806             :   /* Now that the deltas have been applied, we can remove all nodes
     807             :      that are not direct descendants of the new root. */
     808             : 
     809           9 :   fd_bank_t * head = fd_banks_map_ele_remove( bank_map, &old_root->eslot_, NULL, bank_pool );
     810           9 :   head->next       = fd_banks_pool_idx_null( bank_pool );
     811           9 :   fd_bank_t * tail = head;
     812             : 
     813          63 :   while( head ) {
     814          54 :     fd_bank_t * child = fd_banks_pool_ele( bank_pool, head->child_idx );
     815             : 
     816         108 :     while( FD_LIKELY( child ) ) {
     817             : 
     818          54 :       if( FD_LIKELY( child!=new_root ) ) {
     819          45 :         if( FD_UNLIKELY( child->refcnt!=0UL ) ) {
     820           0 :           FD_LOG_CRIT(( "refcnt for child bank (slot %lu, prime %lu) is %lu", (ulong)child->eslot_.slot, (ulong)child->eslot_.prime, child->refcnt ));
     821           0 :         }
     822             : 
     823             :         /* Remove the child from the map first and push onto the
     824             :            frontier list that needs to be iterated through */
     825          45 :         tail->next = fd_banks_map_idx_remove(
     826          45 :             bank_map,
     827          45 :             &child->eslot_,
     828          45 :             fd_banks_pool_idx_null( bank_pool ),
     829          45 :             bank_pool );
     830             : 
     831          45 :         tail       = fd_banks_pool_ele( bank_pool, tail->next );
     832          45 :         tail->next = fd_banks_pool_idx_null( bank_pool );
     833             : 
     834          45 :       }
     835             : 
     836          54 :       child = fd_banks_pool_ele( bank_pool, child->sibling_idx );
     837          54 :     }
     838             : 
     839          54 :     fd_bank_t * next = fd_banks_pool_ele( bank_pool, head->next );
     840             : 
     841             :     /* Decide if we need to free any CoW fields. We free a CoW member
     842             :        from its pool if the dirty flag is set unless it is the same
     843             :        pool that the new root uses. */
     844          54 :     #define HAS_COW_1(name)                                                          \
     845         540 :       if( head->name##_dirty && head->name##_pool_idx!=new_root->name##_pool_idx ) { \
     846           3 :         fd_bank_##name##_t * name##_pool = fd_banks_get_##name##_pool( banks );      \
     847           3 :         fd_bank_##name##_pool_idx_release( name##_pool, head->name##_pool_idx );     \
     848           3 :       }
     849             :     /* Do nothing for these. */
     850          54 :     #define HAS_COW_0(name)
     851             : 
     852          54 :     #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     853         270 :       HAS_COW_##cow(name)
     854         270 :     FD_BANKS_ITER(X)
     855          54 :     #undef X
     856          54 :     #undef HAS_COW_0
     857          54 :     #undef HAS_COW_1
     858             : 
     859          54 :     fd_banks_pool_ele_release( bank_pool, head );
     860          54 :     head = next;
     861          54 :   }
     862             : 
     863             :   /* If the new root did not have the dirty bit set, that means the node
     864             :      didn't own the pool index. Change the ownership to the new root. */
     865           9 :   #define HAS_COW_1(name)                                                            \
     866          45 :     fd_bank_##name##_t * name##_pool = fd_banks_get_##name##_pool( banks );          \
     867          45 :     if( new_root->name##_pool_idx!=fd_bank_##name##_pool_idx_null( name##_pool ) ) { \
     868           3 :       new_root->name##_dirty = 1;                                                    \
     869           3 :     }
     870             :   /* Do nothing if not CoW. */
     871           9 :   #define HAS_COW_0(name)
     872             : 
     873           9 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     874          45 :     HAS_COW_##cow(name)
     875          45 :   FD_BANKS_ITER(X)
     876           9 :   #undef X
     877           9 :   #undef HAS_COW_0
     878           9 :   #undef HAS_COW_1
     879             : 
     880           9 :   new_root->parent_idx = null_idx;
     881           9 :   banks->root_idx      = fd_banks_map_idx_query_const( bank_map, &eslot, null_idx, bank_pool );
     882             : 
     883           9 :   fd_rwlock_unwrite( &banks->rwlock );
     884             : 
     885           9 :   return new_root;
     886           9 : }
     887             : 
     888             : void
     889           3 : fd_banks_clear_bank( fd_banks_t * banks, fd_bank_t * bank ) {
     890             : 
     891           3 :   fd_rwlock_read( &banks->rwlock );
     892             :   /* Get the parent bank. */
     893           3 :   fd_bank_t * parent_bank = fd_banks_pool_ele( fd_banks_get_bank_pool( banks ), bank->parent_idx );
     894             : 
     895           3 :   #define HAS_COW_1(type, name, footprint)                                                                                  \
     896          15 :     fd_bank_##name##_t * name##_pool = fd_bank_get_##name##_pool( bank );                                                   \
     897          15 :     if( bank->name##_dirty ) {                                                                                              \
     898             :       /* If the dirty flag is set, then we have a pool allocated for */                                                     \
     899             :       /* this specific bank. We need to release the pool index and   */                                                     \
     900             :       /* assign the bank to the idx corresponding to the parent.     */                                                     \
     901           6 :       fd_bank_##name##_pool_idx_release( name##_pool, bank->name##_pool_idx );                                              \
     902           6 :       bank->name##_dirty    = 0;                                                                                            \
     903           6 :       bank->name##_pool_idx = parent_bank ? parent_bank->name##_pool_idx : fd_bank_##name##_pool_idx_null( name##_pool );   \
     904           6 :     }
     905             : 
     906           3 :   #define HAS_COW_0(type, name, footprint) \
     907         126 :     fd_memset( bank->name, 0, footprint );
     908             : 
     909           3 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
     910         141 :     HAS_COW_##cow(type, name, footprint)
     911         141 :   FD_BANKS_ITER(X)
     912           3 :   #undef X
     913           3 :   #undef HAS_COW_0
     914           3 :   #undef HAS_COW_1
     915             : 
     916           3 :   fd_rwlock_unread( &banks->rwlock );
     917           3 : }
     918             : 
     919             : /* Is the fork tree starting at the given bank entirely eligible for
     920             :    pruning?  Returns 1 for yes, 0 for no.
     921             : 
     922             :    See comment in fd_replay_tile.c for more details on safe pruning. */
     923             : static int
     924          30 : fd_banks_subtree_can_be_pruned( fd_bank_t * bank_pool, fd_bank_t * bank ) {
     925          30 :   if( FD_UNLIKELY( !bank ) ) {
     926           0 :     FD_LOG_CRIT(( "invariant violation: bank is NULL" ));
     927           0 :   }
     928             : 
     929          30 :   if( bank->refcnt!=0UL ) {
     930           6 :     return 0;
     931           6 :   }
     932             : 
     933             :   /* Recursively check all children. */
     934          24 :   ulong child_idx = bank->child_idx;
     935          33 :   while( child_idx!=fd_banks_pool_idx_null( bank_pool ) ) {
     936           9 :     fd_bank_t * child = fd_banks_pool_ele( bank_pool, child_idx );
     937           9 :     if( !fd_banks_subtree_can_be_pruned( bank_pool, child ) ) {
     938           0 :       return 0;
     939           0 :     }
     940           9 :     child_idx = child->sibling_idx;
     941           9 :   }
     942             : 
     943          24 :   return 1;
     944          24 : }
     945             : 
     946             : /* Mark everything in the fork tree starting at the given bank dead. */
     947             : 
     948             : static void
     949          51 : fd_banks_subtree_mark_dead( fd_bank_t * bank_pool, fd_bank_t * bank ) {
     950          51 :   if( FD_UNLIKELY( !bank ) ) {
     951           0 :     FD_LOG_CRIT(( "invariant violation: bank is NULL" ));
     952           0 :   }
     953          51 :   if( FD_UNLIKELY( bank->flags & FD_BANK_FLAGS_ROOTED ) ) {
     954           0 :     FD_LOG_CRIT(( "invariant violation: bank for slot %lu is rooted", bank->eslot_.id ));
     955           0 :   }
     956             : 
     957          51 :   bank->flags |= FD_BANK_FLAGS_DEAD;
     958             : 
     959             :   /* Recursively mark all children as dead. */
     960          51 :   ulong child_idx = bank->child_idx;
     961          69 :   while( child_idx!=fd_banks_pool_idx_null( bank_pool ) ) {
     962          18 :     fd_bank_t * child = fd_banks_pool_ele( bank_pool, child_idx );
     963          18 :     fd_banks_subtree_mark_dead( bank_pool, child );
     964          18 :     child_idx = child->sibling_idx;
     965          18 :   }
     966          51 : }
     967             : 
     968             : int
     969             : fd_banks_publish_prepare( fd_banks_t * banks,
     970             :                           fd_eslot_t   target_eslot,
     971           9 :                           fd_eslot_t * publishable_eslot_out ) {
     972             :   /* TODO: An optimization here is to do a single traversal of the tree
     973             :      that would mark minority forks as dead while accumulating
     974             :      refcnts to determine which bank is the highest publishable. */
     975             : 
     976           9 :   fd_bank_t *      bank_pool = fd_banks_get_bank_pool( banks );
     977           9 :   fd_banks_map_t * bank_map  = fd_banks_get_bank_map( banks );
     978             : 
     979           9 :   fd_rwlock_read( &banks->rwlock );
     980             : 
     981           9 :   fd_bank_t * root = fd_banks_root( banks );
     982           9 :   if( FD_UNLIKELY( !root ) ) {
     983           0 :     FD_LOG_WARNING(( "failed to get root bank" ));
     984           0 :     fd_rwlock_unread( &banks->rwlock );
     985           0 :     return 0;
     986           0 :   }
     987             : 
     988             :   /* Early exit if target is the same as the old root. */
     989           9 :   fd_eslot_t root_eslot = fd_bank_eslot_get( root );
     990           9 :   if( FD_UNLIKELY( root_eslot.id==target_eslot.id ) ) {
     991           0 :     FD_LOG_WARNING(( "target block (slot %lu, prime %lu) is the same as the old root (slot %lu, prime %lu)", (ulong)target_eslot.slot, (ulong)target_eslot.prime, (ulong)root_eslot.slot, (ulong)root_eslot.prime ));
     992           0 :     fd_rwlock_unread( &banks->rwlock );
     993           0 :     return 0;
     994           0 :   }
     995             : 
     996           9 :   ulong target_bank_idx = fd_banks_map_idx_query_const( bank_map, &target_eslot, ULONG_MAX, bank_pool );
     997           9 :   if( FD_UNLIKELY( target_bank_idx==ULONG_MAX ) ) {
     998           0 :     FD_LOG_WARNING(( "failed to get bank idx for target (slot %lu, prime %lu)", (ulong)target_eslot.slot, (ulong)target_eslot.prime ));
     999           0 :     fd_rwlock_unread( &banks->rwlock );
    1000           0 :     return 0;
    1001           0 :   }
    1002             : 
    1003           9 :   fd_bank_t * target_bank = fd_banks_pool_ele( bank_pool, target_bank_idx );
    1004           9 :   if( FD_UNLIKELY( !target_bank ) ) {
    1005           0 :     FD_LOG_CRIT(( "failed to get bank for valid pool idx %lu", target_bank_idx ));
    1006           0 :   }
    1007             : 
    1008             :   /* Mark every node from the target bank up through its parents to the
    1009             :      root as being rooted. */
    1010           9 :   fd_bank_t * curr = target_bank;
    1011           9 :   fd_bank_t * prev = NULL;
    1012          45 :   while( curr ) {
    1013          36 :     curr->flags |= FD_BANK_FLAGS_ROOTED;
    1014          36 :     prev         = curr;
    1015          36 :     curr         = fd_banks_pool_ele( bank_pool, curr->parent_idx );
    1016          36 :   }
    1017             : 
    1018             :   /* If we didn't reach the old root, target is not a descendant. */
    1019           9 :   if( FD_UNLIKELY( prev!=root ) ) {
    1020           0 :     FD_LOG_CRIT(( "target (slot %lu, prime %lu) is not a descendant of root (slot %lu, prime %lu)", (ulong)target_eslot.slot, (ulong)target_eslot.prime, (ulong)root_eslot.slot, (ulong)root_eslot.prime ));
    1021           0 :   }
    1022             : 
    1023             :   /* We know that the majority fork that is not getting pruned off is
    1024             :      the child of the target bank.  All other child/sibling nodes off of
    1025             :      the other nodes that were just marked as root are minority forks
    1026             :      which should be pruned off. */
    1027             : 
    1028             :   /* Now traverse from root towards target and find the highest
    1029             :      block that can be pruned. */
    1030           9 :   fd_eslot_t   highest_publishable_eslot = {0};
    1031           9 :   fd_bank_t *  publishable_bank          = NULL;
    1032           9 :   fd_bank_t *  prune_candidate           = root;
    1033           9 :   int          found_publishable_block   = 0;
    1034          24 :   while( prune_candidate && prune_candidate->flags & FD_BANK_FLAGS_ROOTED ) {
    1035          21 :     fd_bank_t * rooted_child_bank = NULL;
    1036             : 
    1037          21 :     if( prune_candidate->refcnt!=0UL ) {
    1038           0 :       break;
    1039           0 :     }
    1040             : 
    1041             :     /* For this node to be pruned, all minority forks that branch off
    1042             :        from it must be entirely eligible for pruning.  A fork is
    1043             :        eligible for pruning if there are no outstanding references to
    1044             :        any of the nodes on the fork.  This means checking all children
    1045             :        (except for the one on the rooted fork) and their entire
    1046             :        subtrees. */
    1047          21 :     int all_minority_forks_can_be_pruned = 1;
    1048          21 :     ulong child_idx = prune_candidate->child_idx;
    1049          48 :     while( child_idx!=fd_banks_pool_idx_null( bank_pool ) ) {
    1050          33 :       fd_bank_t * sibling = fd_banks_pool_ele( bank_pool, child_idx );
    1051          33 :       if( sibling->flags & FD_BANK_FLAGS_ROOTED ) {
    1052          12 :         rooted_child_bank = sibling;
    1053          21 :       } else if( sibling->parent_idx!=target_bank_idx ) {
    1054             :         /* This is a minority fork. */
    1055          21 :         if( !fd_banks_subtree_can_be_pruned( bank_pool, sibling ) ) {
    1056           6 :           all_minority_forks_can_be_pruned = 0;
    1057           6 :           break;
    1058           6 :         }
    1059          21 :       }
    1060          27 :       child_idx = sibling->sibling_idx;
    1061          27 :     }
    1062             : 
    1063          21 :     if( !all_minority_forks_can_be_pruned ) {
    1064           6 :       break;
    1065           6 :     }
    1066             : 
    1067          15 :     highest_publishable_eslot = fd_bank_eslot_get( prune_candidate );
    1068          15 :     publishable_bank          = prune_candidate;
    1069          15 :     prune_candidate           = rooted_child_bank;
    1070          15 :     found_publishable_block   = 1;
    1071          15 :   }
    1072             : 
    1073           9 :   int advanced_publishable_block = 0;
    1074           9 :   if( FD_LIKELY( found_publishable_block ) ) {
    1075             :     /* Find the rooted child of the highest block that can be pruned.
    1076             :        That's where we can publish to. */
    1077           6 :     fd_bank_t * rooted_child_bank = NULL;
    1078           6 :     ulong child_idx = publishable_bank->child_idx;
    1079           6 :     while( child_idx!=fd_banks_pool_idx_null( bank_pool ) ) {
    1080           3 :       fd_bank_t * sibling = fd_banks_pool_ele( bank_pool, child_idx );
    1081           3 :       if( sibling->flags & FD_BANK_FLAGS_ROOTED ) {
    1082           3 :         rooted_child_bank = sibling;
    1083           3 :         break;
    1084           3 :       }
    1085           0 :       child_idx = sibling->sibling_idx;
    1086           0 :     }
    1087           6 :     if( FD_LIKELY( rooted_child_bank ) ) {
    1088           3 :       highest_publishable_eslot = fd_bank_eslot_get( rooted_child_bank );
    1089           3 :     }
    1090             : 
    1091             :     /* Write output. */
    1092           6 :     *publishable_eslot_out = highest_publishable_eslot;
    1093             : 
    1094           6 :    if( FD_LIKELY( highest_publishable_eslot.id!=fd_bank_eslot_get( root ).id ) ) {
    1095           6 :     advanced_publishable_block = 1;
    1096           6 :    }
    1097             : 
    1098           6 :   }
    1099             : 
    1100             :   /* At this point the highest publishable bank has been identified. */
    1101             : 
    1102             :   /* Now mark all minority forks as being dead.  This involves
    1103             :      traversing the tree down from the old root through its descendants
    1104             :      that are marked as rooted.  Any child/sibling nodes of these rooted
    1105             :      nodes are minority forks which should be marked as dead. */
    1106             : 
    1107           9 :   curr = root;
    1108          45 :   while( curr && curr->flags & FD_BANK_FLAGS_ROOTED ) {
    1109          36 :     fd_bank_t * rooted_child_bank = NULL;
    1110          36 :     ulong       child_idx         = curr->child_idx;
    1111          96 :     while( child_idx!=fd_banks_pool_idx_null( bank_pool ) ) {
    1112          60 :       fd_bank_t * sibling = fd_banks_pool_ele( bank_pool, child_idx );
    1113          60 :       if( sibling->flags & FD_BANK_FLAGS_ROOTED ) {
    1114          27 :         rooted_child_bank = sibling;
    1115          33 :       } else if( sibling->parent_idx!=target_bank_idx ) {
    1116             :         /* This is a minority fork.  Every node in the subtree should
    1117             :            be marked as dead.  We know that it is a minority fork
    1118             :            this node is not a child of the new target root. */
    1119          33 :         fd_banks_subtree_mark_dead( bank_pool, sibling );
    1120          33 :       }
    1121          60 :       child_idx = sibling->sibling_idx;
    1122          60 :     }
    1123          36 :     curr = rooted_child_bank;
    1124          36 :   }
    1125             : 
    1126           9 :   fd_rwlock_unread( &banks->rwlock );
    1127           9 :   return advanced_publishable_block;
    1128           9 : }
    1129             : 
    1130             : fd_bank_t *
    1131             : fd_banks_rekey_bank( fd_banks_t * banks,
    1132             :                      fd_eslot_t   old_eslot,
    1133           9 :                      fd_eslot_t   new_eslot ) {
    1134           9 :   fd_rwlock_write( &banks->rwlock );
    1135             : 
    1136           9 :   fd_banks_map_t * bank_map  = fd_banks_get_bank_map( banks );
    1137           9 :   fd_bank_t *      bank_pool = fd_banks_get_bank_pool( banks );
    1138             : 
    1139           9 :   fd_bank_t * bank = fd_banks_map_ele_remove( bank_map, &old_eslot, NULL, bank_pool );
    1140           9 :   if( FD_UNLIKELY( !bank ) ) {
    1141           0 :     FD_LOG_CRIT(( "invariant violation: failed to remove bank from map" ));
    1142           0 :   }
    1143             : 
    1144           9 :   bank->eslot_ = new_eslot;
    1145             : 
    1146           9 :   fd_banks_map_ele_insert( bank_map, bank, bank_pool );
    1147             : 
    1148           9 :   fd_rwlock_unwrite( &banks->rwlock );
    1149             : 
    1150           9 :   return bank;
    1151           9 : }
    1152             : 
    1153             : void
    1154             : fd_banks_mark_bank_dead( fd_banks_t * banks,
    1155           0 :                          fd_bank_t *  bank ) {
    1156           0 :   fd_rwlock_write( &banks->rwlock );
    1157             : 
    1158           0 :   fd_banks_subtree_mark_dead( fd_banks_get_bank_pool( banks ), bank );
    1159             : 
    1160           0 :   fd_rwlock_unwrite( &banks->rwlock );
    1161           0 : }
    1162             : 
    1163             : static char *
    1164           0 : fd_banks_flags_to_str( fd_bank_t const * bank ) {
    1165           0 :   switch( bank->flags ) {
    1166           0 :     case FD_BANK_FLAGS_INIT:
    1167           0 :       return "I";
    1168           0 :     case FD_BANK_FLAGS_FROZEN:
    1169           0 :       return "F";
    1170           0 :     case FD_BANK_FLAGS_DEAD:
    1171           0 :       return "D";
    1172           0 :     case FD_BANK_FLAGS_ROOTED:
    1173           0 :       return "R";
    1174           0 :     case FD_BANK_FLAGS_DEAD + FD_BANK_FLAGS_ROOTED:
    1175           0 :       return "D + R";
    1176           0 :     case FD_BANK_FLAGS_ROOTED + FD_BANK_FLAGS_FROZEN:
    1177           0 :       return "R + F";
    1178           0 :     default:
    1179           0 :       return "U";
    1180           0 :   }
    1181           0 : }
    1182             : 
    1183             : static void
    1184             : fd_banks_print_helper( fd_bank_t const *  bank,
    1185             :                        fd_banks_t const * banks,
    1186             :                        fd_bank_t const *  bank_pool,
    1187           0 :                        int                space ) {
    1188           0 :   if( FD_UNLIKELY( !bank ) ) {
    1189           0 :     return;
    1190           0 :   }
    1191           0 :   if( space>0 ) {
    1192           0 :     printf( "\n" );
    1193           0 :   }
    1194           0 :   for( int i=0; i<space; i++ ) {
    1195           0 :     printf( " " );
    1196           0 :   }
    1197           0 :   printf( "(Slot: %lu, Prime %u, Flags: %s)", fd_bank_slot_get( bank ), fd_bank_prime_get( bank ), fd_banks_flags_to_str( bank ) );
    1198             : 
    1199           0 :   fd_bank_t const * curr = fd_banks_pool_ele_const( bank_pool, bank->child_idx );
    1200           0 :   while( curr ) {
    1201           0 :     if( fd_banks_pool_ele_const( bank_pool, curr->sibling_idx ) ) {
    1202           0 :       printf( "├── " ); /* branch indicating more siblings follow */
    1203           0 :       fd_banks_print_helper( curr, banks, bank_pool, space + 4 );
    1204           0 :     } else {
    1205           0 :       printf( "└── " ); /* end branch */
    1206           0 :       fd_banks_print_helper( curr, banks, bank_pool, space + 4 );
    1207           0 :     }
    1208           0 :     curr = fd_banks_pool_ele_const( bank_pool, curr->sibling_idx );
    1209           0 :   }
    1210           0 : }
    1211             : 
    1212             : void
    1213           0 : fd_banks_print( fd_banks_t * banks ) {
    1214           0 :   fd_rwlock_read( &banks->rwlock );
    1215           0 :   fd_bank_t * bank_pool = fd_banks_get_bank_pool( banks );
    1216           0 :   fd_bank_t * bank      = fd_banks_root( banks );
    1217             : 
    1218           0 :   printf( "\n\n[Banks]\n" );
    1219             : 
    1220           0 :   fd_banks_print_helper( bank, banks, bank_pool, 0 );
    1221             : 
    1222           0 :   printf( "\n\n" );
    1223             : 
    1224           0 :   fd_rwlock_unread( &banks->rwlock );
    1225           0 : }
    1226             : 
    1227             : int
    1228           0 : fd_banks_validate( fd_banks_t * banks ) {
    1229           0 :   fd_rwlock_read( &banks->rwlock );
    1230             : 
    1231           0 :   fd_bank_t * bank_pool = fd_banks_get_bank_pool( banks );
    1232             : 
    1233           0 :   FD_LOG_INFO(( "fd_banks_pool_free: %lu", fd_banks_pool_free( bank_pool ) ));
    1234             : 
    1235             :   /* First check that the number of elements acquired by the CoW pools
    1236             :      is not greater than the number of elements in the bank pool. */
    1237           0 :   #define HAS_COW_1(type, name, footprint)                                                                                                                                                      \
    1238           0 :   fd_bank_##name##_t * name##_pool = fd_bank_get_##name##_pool( bank );                                                                                                                         \
    1239           0 :   if( fd_bank_##name##_pool_used( name##_pool ) > fd_bank_pool_used( bank_pool ) ) {                                                                                                            \
    1240           0 :     FD_LOG_WARNING(( "Invariant violation: %s pool has more elements acquired than the bank pool %lu %lu", #name, fd_bank_##name##_pool_used( name##_pool ), fd_bank_pool_used( bank_pool ) )); \
    1241           0 :     fd_rwlock_unread( &banks->rwlock );                                                                                                                                                         \
    1242           0 :     return 1;                                                                                                                                                                                   \
    1243           0 :   }                                                                                                                                                                                             \
    1244           0 : 
    1245           0 :   #define HAS_COW_0(type, name, footprint)
    1246             : 
    1247           0 :   #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
    1248           0 :     HAS_COW_##cow(type, name, footprint)                                   \
    1249           0 :   FD_BANKS_ITER(X)
    1250           0 :   #undef X
    1251           0 :   #undef HAS_COW_0
    1252           0 :   #undef HAS_COW_1
    1253           0 :   fd_rwlock_unread( &banks->rwlock );
    1254             : 
    1255           0 :   return 0;
    1256           0 : }

Generated by: LCOV version 1.14