LCOV - code coverage report
Current view: top level - app/shared - fd_config.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 295 568 51.9 %
Date: 2026-09-14 04:30:10 Functions: 11 13 84.6 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "fd_config.h"
       3             : #include "fd_config_auto.h"
       4             : #include "fd_config_private.h"
       5             : 
       6             : #include "../platform/fd_net_util.h"
       7             : #include "../platform/fd_sys_util.h"
       8             : #include "../../ballet/toml/fd_toml.h"
       9             : #include "../../disco/genesis/fd_genesis_cluster.h"
      10             : #include "../../discof/genesis/fd_genesi_tile.h"
      11             : #include "../../disco/net/fd_net_tile.h"
      12             : #include "../../disco/pack/fd_pack_cost.h"
      13             : #include "../../disco/pack/fd_microblock.h"
      14             : #include "../../ballet/shred/fd_shred.h"
      15             : #include "../../ballet/txn/fd_txn.h"
      16             : #include "../../discof/restore/utils/fd_ssarchive.h"
      17             : 
      18             : #include <unistd.h>
      19             : #include <errno.h>
      20             : #include <stdlib.h> /* strtoul */
      21             : #include <sys/utsname.h>
      22             : #include <sys/mman.h>
      23             : 
      24             : /* TODO: Rewrite this ... */
      25             : 
      26             : static inline void
      27             : replace( char *       in,
      28             :          const char * pat,
      29          18 :          const char * sub ) {
      30          18 :   char * replace = strstr( in, pat );
      31          18 :   if( FD_LIKELY( replace ) ) {
      32           6 :     ulong pat_len = strlen( pat );
      33           6 :     ulong sub_len = strlen( sub );
      34           6 :     ulong in_len  = strlen( in );
      35           6 :     if( FD_UNLIKELY( pat_len > in_len ) ) return;
      36             : 
      37           6 :     ulong total_len = in_len - pat_len + sub_len;
      38           6 :     if( FD_UNLIKELY( total_len >= PATH_MAX ) )
      39           0 :       FD_LOG_ERR(( "configuration scratch directory path too long: `%s`", in ));
      40             : 
      41           6 :     uchar after[PATH_MAX] = {0};
      42           6 :     fd_memcpy( after, replace + pat_len, strlen( replace + pat_len ) );
      43           6 :     fd_memcpy( replace, sub, sub_len );
      44           6 :     ulong after_len = strlen( ( const char * ) after );
      45           6 :     fd_memcpy( replace + sub_len, after, after_len );
      46           6 :     in[ total_len ] = '\0';
      47           6 :   }
      48          18 : }
      49             : 
      50             : 
      51             : FD_FN_CONST static inline int
      52           6 : parse_log_level( char const * level ) {
      53           6 :   if( FD_UNLIKELY( !strcmp( level, "DEBUG" ) ) )    return 0;
      54           6 :   if( FD_UNLIKELY( !strcmp( level, "INFO"  ) ) )    return 1;
      55           3 :   if( FD_UNLIKELY( !strcmp( level, "NOTICE"  ) ) )  return 2;
      56           0 :   if( FD_UNLIKELY( !strcmp( level, "WARNING"  ) ) ) return 3;
      57           0 :   if( FD_UNLIKELY( !strcmp( level, "ERR" ) ) )      return 4;
      58           0 :   if( FD_UNLIKELY( !strcmp( level, "CRIT" ) ) )     return 5;
      59           0 :   if( FD_UNLIKELY( !strcmp( level, "ALERT" ) ) )    return 6;
      60           0 :   if( FD_UNLIKELY( !strcmp( level, "EMERG" ) ) )    return 7;
      61           0 :   return -1;
      62           0 : }
      63             : 
      64             : FD_FN_CONST static inline int
      65           3 : parse_core_dump_level( char const * level ) {
      66           3 :   if( FD_UNLIKELY( !strcmp( level, "disabled" ) ) )       return FD_TOPO_CORE_DUMP_LEVEL_DISABLED;
      67           0 :   if( FD_UNLIKELY( !strcmp( level, "minimal"  ) ) )       return FD_TOPO_CORE_DUMP_LEVEL_MINIMAL;
      68           0 :   if( FD_UNLIKELY( !strcmp( level, "regular"  ) ) )       return FD_TOPO_CORE_DUMP_LEVEL_REGULAR;
      69           0 :   if( FD_UNLIKELY( !strcmp( level, "full"     ) ) )       return FD_TOPO_CORE_DUMP_LEVEL_FULL;
      70           0 :   return -1;
      71           0 : }
      72             : 
      73             : void
      74             : fd_config_load_buf( fd_config_t * out,
      75             :                     char const *  buf,
      76             :                     ulong         sz,
      77           3 :                     char const *  path ) {
      78           3 :   static uchar pod_mem[ 1UL<<26 ];
      79           3 :   uchar * pod = fd_pod_join( fd_pod_new( pod_mem, sizeof(pod_mem) ) );
      80             : 
      81           3 :   fd_toml_err_info_t toml_err[1];
      82           3 :   uchar scratch[ 4096 ];
      83           3 :   int toml_errc = fd_toml_parse( buf, sz, pod, scratch, sizeof(scratch), toml_err );
      84           3 :   if( FD_UNLIKELY( toml_errc!=FD_TOML_SUCCESS ) ) {
      85           0 :     switch( toml_errc ) {
      86           0 :     case FD_TOML_ERR_POD:
      87           0 :       FD_LOG_ERR(( "Failed to parse config file (%s): ran out of buffer space while parsing", path ));
      88           0 :       break;
      89           0 :     case FD_TOML_ERR_SCRATCH:
      90           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: ran out of scratch space while parsing", path, toml_err->line ));
      91           0 :       break;
      92           0 :     case FD_TOML_ERR_KEY:
      93           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: oversize key", path, toml_err->line ));
      94           0 :       break;
      95           0 :     case FD_TOML_ERR_DUP:
      96           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: duplicate key", path, toml_err->line ));
      97           0 :       break;
      98           0 :     case FD_TOML_ERR_RANGE:
      99           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: invalid value for key", path, toml_err->line ));
     100           0 :       break;
     101           0 :     case FD_TOML_ERR_PARSE:
     102           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu", path, toml_err->line ));
     103           0 :       break;
     104           0 :     default:
     105           0 :       FD_LOG_ERR(( "Failed to parse config file (%s): %s", path, fd_toml_strerror( toml_errc ) ));
     106           0 :       break;
     107           0 :     }
     108           0 :   }
     109             : 
     110           3 :   if( FD_UNLIKELY( !fd_config_extract_pod( pod, out ) ) ) FD_LOG_ERR(( "Failed to parse config file (%s): there are unrecognized keys logged above", path ));
     111             : 
     112           3 :   fd_pod_delete( fd_pod_leave( pod ) );
     113           3 : }
     114             : 
     115             : static void
     116           3 : fd_config_fillf( fd_config_t * config ) {
     117           3 :   if( FD_UNLIKELY( strcmp( config->paths.accounts, "" ) ) ) {
     118           0 :     replace( config->paths.accounts, "{user}", config->user );
     119           0 :     replace( config->paths.accounts, "{name}", config->name );
     120           3 :   } else {
     121           3 :     FD_TEST( fd_cstr_printf_check( config->paths.accounts, sizeof(config->paths.accounts), NULL, "%s/accounts.db", config->paths.base ) );
     122           3 :   }
     123             : 
     124           3 :   if( FD_UNLIKELY( strcmp( config->paths.shredb, "" ) ) ) {
     125           0 :     replace( config->paths.shredb, "{user}", config->user );
     126           0 :     replace( config->paths.shredb, "{name}", config->name );
     127           3 :   } else {
     128           3 :     FD_TEST( fd_cstr_printf_check( config->paths.shredb, sizeof(config->paths.shredb), NULL, "%s/shreds.db", config->paths.base ) );
     129           3 :   }
     130             : 
     131           3 :   if( FD_UNLIKELY( strcmp( config->paths.guidb, "" ) ) ) {
     132           0 :     replace( config->paths.guidb, "{user}", config->user );
     133           0 :     replace( config->paths.guidb, "{name}", config->name );
     134           3 :   } else {
     135           3 :     FD_TEST( fd_cstr_printf_check( config->paths.guidb, sizeof(config->paths.guidb), NULL, "%s/gui.db", config->paths.base ) );
     136           3 :   }
     137             : 
     138           3 :   for( ulong i=0UL; i<config->firedancer.paths.authorized_voter_paths_cnt; i++ ) {
     139           0 :     replace( config->firedancer.paths.authorized_voter_paths[ i ], "{user}", config->user );
     140           0 :     replace( config->firedancer.paths.authorized_voter_paths[ i ], "{name}", config->name );
     141           0 :   }
     142           3 : }
     143             : 
     144             : static void
     145           0 : fd_config_fillh( fd_config_t * config ) {
     146           0 :   if( FD_UNLIKELY( strcmp( config->frankendancer.paths.accounts_path, "" ) ) ) {
     147           0 :     replace( config->frankendancer.paths.accounts_path, "{user}", config->user );
     148           0 :     replace( config->frankendancer.paths.accounts_path, "{name}", config->name );
     149           0 :   }
     150             : 
     151           0 :   if( FD_UNLIKELY( strcmp( config->frankendancer.paths.ledger, "" ) ) ) {
     152           0 :     replace( config->frankendancer.paths.ledger, "{user}", config->user );
     153           0 :     replace( config->frankendancer.paths.ledger, "{name}", config->name );
     154           0 :   } else {
     155           0 :     FD_TEST( fd_cstr_printf_check( config->frankendancer.paths.ledger, sizeof(config->frankendancer.paths.ledger), NULL, "%s/ledger", config->paths.base ) );
     156           0 :   }
     157             : 
     158           0 :   if( FD_UNLIKELY( strcmp( config->frankendancer.snapshots.path, "" ) ) ) {
     159           0 :     replace( config->frankendancer.snapshots.path, "{user}", config->user );
     160           0 :     replace( config->frankendancer.snapshots.path, "{name}", config->name );
     161           0 :   } else {
     162           0 :     strncpy( config->frankendancer.snapshots.path, config->frankendancer.paths.ledger, sizeof(config->frankendancer.snapshots.path) );
     163           0 :   }
     164             : 
     165           0 :   for( ulong i=0UL; i<config->frankendancer.paths.authorized_voter_paths_cnt; i++ ) {
     166           0 :     replace( config->frankendancer.paths.authorized_voter_paths[ i ], "{user}", config->user );
     167           0 :     replace( config->frankendancer.paths.authorized_voter_paths[ i ], "{name}", config->name );
     168           0 :   }
     169             : 
     170           0 :   if( FD_UNLIKELY( config->tiles.quic.quic_transaction_listen_port!=config->tiles.quic.regular_transaction_listen_port+6 ) )
     171           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.quic_transaction_listen_port] `%hu`. "
     172           0 :                  "This must be 6 more than [tiles.quic.regular_transaction_listen_port] `%hu`",
     173           0 :                  config->tiles.quic.quic_transaction_listen_port,
     174           0 :                  config->tiles.quic.regular_transaction_listen_port ));
     175             : 
     176           0 :   char dynamic_port_range[ 32 ];
     177           0 :   fd_memcpy( dynamic_port_range, config->frankendancer.dynamic_port_range, sizeof(dynamic_port_range) );
     178             : 
     179           0 :   char * dash = strstr( dynamic_port_range, "-" );
     180           0 :   if( FD_UNLIKELY( !dash ) )
     181           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     182           0 :                  "This must be formatted like `<min>-<max>`",
     183           0 :                  config->frankendancer.dynamic_port_range ));
     184             : 
     185           0 :   *dash = '\0';
     186           0 :   char * endptr;
     187           0 :   ulong agave_port_min = strtoul( dynamic_port_range, &endptr, 10 );
     188           0 :   if( FD_UNLIKELY( *endptr != '\0' || agave_port_min > USHORT_MAX ) )
     189           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     190           0 :                  "This must be formatted like `<min>-<max>`",
     191           0 :                  config->frankendancer.dynamic_port_range ));
     192           0 :   ulong agave_port_max = strtoul( dash + 1, &endptr, 10 );
     193           0 :   if( FD_UNLIKELY( *endptr != '\0' || agave_port_max > USHORT_MAX ) )
     194           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     195           0 :                  "This must be formatted like `<min>-<max>`",
     196           0 :                  config->frankendancer.dynamic_port_range ));
     197           0 :   if( FD_UNLIKELY( agave_port_min > agave_port_max ) )
     198           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     199           0 :                  "The minimum port must be less than or equal to the maximum port",
     200           0 :                  config->frankendancer.dynamic_port_range ));
     201             : 
     202           0 :   if( FD_UNLIKELY( config->tiles.quic.regular_transaction_listen_port >= agave_port_min &&
     203           0 :                    config->tiles.quic.regular_transaction_listen_port < agave_port_max ) )
     204           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.transaction_listen_port] `%hu`. "
     205           0 :                  "This must be outside the dynamic port range `%s`",
     206           0 :                  config->tiles.quic.regular_transaction_listen_port,
     207           0 :                  config->frankendancer.dynamic_port_range ));
     208             : 
     209           0 :   if( FD_UNLIKELY( config->tiles.quic.quic_transaction_listen_port >= agave_port_min &&
     210           0 :                    config->tiles.quic.quic_transaction_listen_port < agave_port_max ) )
     211           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.quic_transaction_listen_port] `%hu`. "
     212           0 :                  "This must be outside the dynamic port range `%s`",
     213           0 :                  config->tiles.quic.quic_transaction_listen_port,
     214           0 :                  config->frankendancer.dynamic_port_range ));
     215             : 
     216           0 :   if( FD_UNLIKELY( config->tiles.shred.shred_listen_port >= agave_port_min &&
     217           0 :                    config->tiles.shred.shred_listen_port < agave_port_max ) )
     218           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.shred.shred_listen_port] `%hu`. "
     219           0 :                  "This must be outside the dynamic port range `%s`",
     220           0 :                  config->tiles.shred.shred_listen_port,
     221           0 :                  config->frankendancer.dynamic_port_range ));
     222           0 : }
     223             : 
     224             : static void
     225           3 : fd_config_fill_net( fd_config_t * config ) {
     226           3 :   if( FD_UNLIKELY( !strcmp( config->net.interface, "" ) ) ) {
     227           3 :     uint ifindex;
     228           3 :     int result = fd_net_util_internet_ifindex( &ifindex );
     229           3 :     if( FD_UNLIKELY( -1==result && errno!=ENODEV ) ) FD_LOG_ERR(( "could not get network device index (%i-%s)", errno, fd_io_strerror( errno ) ));
     230           3 :     else if( FD_UNLIKELY( -1==result ) )
     231           0 :       FD_LOG_ERR(( "no network device found which routes to 8.8.8.8. If no network "
     232           3 :                    "interface is specified in the configuration file, Firedancer "
     233           3 :                    "tries to use the first network interface found which routes to "
     234           3 :                    "8.8.8.8. You can see what this is by running `ip route get 8.8.8.8` "
     235           3 :                    "You can fix this error by specifying a network interface to bind to in "
     236           3 :                    "your configuration file under [net.interface]" ));
     237             : 
     238           3 :     if( FD_UNLIKELY( !if_indextoname( ifindex, config->net.interface ) ) )
     239           0 :       FD_LOG_ERR(( "could not get name of interface with index %u", ifindex ));
     240           3 :   }
     241             : 
     242           3 :   if( FD_UNLIKELY( !if_nametoindex( config->net.interface ) ) )
     243           0 :     FD_LOG_ERR(( "configuration specifies network interface `%s` which does not exist", config->net.interface ));
     244           3 :   uint iface_ip;
     245           3 :   if( FD_UNLIKELY( -1==fd_net_util_if_addr( config->net.interface, &iface_ip ) ) )
     246           0 :     FD_LOG_ERR(( "could not get IP address for interface `%s`", config->net.interface ));
     247             : 
     248           3 :   if( FD_UNLIKELY( config->is_firedancer ) ) {
     249           3 :     if( FD_UNLIKELY( strcmp( config->firedancer.gossip.host, "" ) ) ) {
     250           0 :       uint gossip_ip_addr = iface_ip;
     251           0 :       int  has_gossip_ip4 = 0;
     252           0 :       if( FD_UNLIKELY( strlen( config->firedancer.gossip.host )<=15UL ) ) {
     253             :         /* Only sets gossip_ip_addr if it's a valid IPv4 address, otherwise assume it's a DNS name */
     254           0 :         has_gossip_ip4 = fd_cstr_to_ip4_addr( config->firedancer.gossip.host, &gossip_ip_addr );
     255           0 :       }
     256           0 :       if( FD_UNLIKELY( !fd_ip4_addr_is_public( gossip_ip_addr ) && config->is_live_cluster && has_gossip_ip4 ) )
     257           0 :         FD_LOG_ERR(( "Trying to use [gossip.host] " FD_IP4_ADDR_FMT " for listening to incoming "
     258           0 :                      "transactions, but it is part of a private network and will not be routable "
     259           0 :                      "for other Solana network nodes.", FD_IP4_ADDR_FMT_ARGS( gossip_ip_addr ) ));
     260           3 :     } else if( FD_UNLIKELY( !fd_ip4_addr_is_public( iface_ip ) && config->is_live_cluster ) ) {
     261           0 :       FD_LOG_ERR(( "Trying to use network interface `%s` for listening to incoming transactions, "
     262           0 :                    "but it has IPv4 address " FD_IP4_ADDR_FMT " which is part of a private network "
     263           0 :                    "and will not be routable for other Solana network nodes. If you are running "
     264           0 :                    "behind a NAT and this interface is publicly reachable, you can continue by "
     265           0 :                    "manually specifying the IP address to advertise in your configuration under "
     266           0 :                    "[gossip.host].", config->net.interface, FD_IP4_ADDR_FMT_ARGS( iface_ip ) ));
     267           0 :     }
     268           3 :   }
     269             : 
     270           3 :   config->net.ip_addr = iface_ip;
     271           3 : }
     272             : 
     273             : void
     274             : fd_config_fill( fd_config_t * config,
     275             :                 int           is_local_cluster,
     276           3 :                 int           dev ) {
     277           3 :   struct utsname utsname;
     278           3 :   if( FD_UNLIKELY( -1==uname( &utsname ) ) )
     279           0 :     FD_LOG_ERR(( "could not get uname (%i-%s)", errno, fd_io_strerror( errno ) ));
     280           3 :   fd_cstr_ncpy( config->hostname, utsname.nodename, sizeof(config->hostname) ); /* Just truncate the name if it's too long to fit */
     281             : 
     282           3 :   ulong cluster;
     283           3 :   if( FD_UNLIKELY( !config->is_firedancer ) ) cluster = fd_genesis_cluster_identify( config->frankendancer.consensus.expected_genesis_hash );
     284           3 :   else                                        cluster = fd_genesis_cluster_identify( config->consensus.expected_genesis_hash );
     285           3 :   config->is_live_cluster = cluster!=FD_CLUSTER_UNKNOWN;
     286           3 :   strcpy( config->cluster, fd_genesis_cluster_name( cluster ) );
     287             : 
     288           3 :   if( FD_UNLIKELY( !strcmp( config->user, "" ) ) ) {
     289           3 :     const char * user = fd_sys_util_login_user();
     290           3 :     if( FD_UNLIKELY( !user ) )                                                                 FD_LOG_ERR(( "could not automatically determine a user to run Firedancer as. You must specify a [user] in your configuration TOML file." ));
     291           3 :     if( FD_UNLIKELY( strlen( user )>=sizeof( config->user ) ) )                                FD_LOG_ERR(( "user name `%s` is too long", user ));
     292           3 :     strncpy( config->user, user, sizeof(config->user) );
     293           3 :   }
     294             : 
     295           3 :   if( FD_UNLIKELY( -1==fd_sys_util_user_to_uid( config->user, &config->uid, &config->gid ) ) ) FD_LOG_ERR(( "configuration file wants firedancer to run as user `%s` but it does not exist", config->user ));
     296           3 :   if( FD_UNLIKELY( !config->uid || !config->gid ) )                                            FD_LOG_ERR(( "firedancer cannot run as root. please specify a non-root user in the configuration file" ));
     297           3 :   if( FD_UNLIKELY( getuid()!=0U && config->uid!=getuid() ) )                                   FD_LOG_ERR(( "running as uid %u, but config specifies uid %u", getuid(), config->uid ));
     298           3 :   if( FD_UNLIKELY( getgid()!=0U && config->gid!=getgid() ) )                                   FD_LOG_ERR(( "running as gid %u, but config specifies gid %u", getgid(), config->gid ));
     299             : 
     300           3 :   FD_TEST( fd_cstr_printf_check( config->hugetlbfs.gigantic_page_mount_path,
     301           3 :     sizeof(config->hugetlbfs.gigantic_page_mount_path),
     302           3 :     NULL,
     303           3 :     "%s/.gigantic",
     304           3 :     config->hugetlbfs.mount_path ) );
     305           3 :   FD_TEST( fd_cstr_printf_check( config->hugetlbfs.huge_page_mount_path,
     306           3 :     sizeof(config->hugetlbfs.huge_page_mount_path),
     307           3 :     NULL,
     308           3 :     "%s/.huge",
     309           3 :     config->hugetlbfs.mount_path ) );
     310             : 
     311           3 :   ulong max_page_sz = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size );
     312           3 :   if( FD_UNLIKELY( max_page_sz!=FD_SHMEM_HUGE_PAGE_SZ && max_page_sz!=FD_SHMEM_GIGANTIC_PAGE_SZ ) ) FD_LOG_ERR(( "[hugetlbfs.max_page_size] must be \"huge\" or \"gigantic\"" ));
     313             : 
     314           3 :   replace( config->log.path, "{user}", config->user );
     315           3 :   replace( config->log.path, "{name}", config->name );
     316             : 
     317           3 :   if( FD_LIKELY( !strcmp( "auto", config->log.colorize ) ) )       config->log.colorize1 = 2;
     318           0 :   else if( FD_LIKELY( !strcmp( "true", config->log.colorize ) ) )  config->log.colorize1 = 1;
     319           0 :   else if( FD_LIKELY( !strcmp( "false", config->log.colorize ) ) ) config->log.colorize1 = 0;
     320           0 :   else  FD_LOG_ERR(( "[log.colorize] must be one of \"auto\", \"true\", or \"false\"" ));
     321             : 
     322           3 :   if( FD_LIKELY( 2==config->log.colorize1 ) ) {
     323           3 :     config->log.colorize1 = fd_log_should_colorize();
     324           3 :   }
     325             : 
     326           3 :   config->log.level_logfile1 = parse_log_level( config->log.level_logfile );
     327           3 :   config->log.level_stderr1  = parse_log_level( config->log.level_stderr );
     328           3 :   if( FD_UNLIKELY( !strcmp( config->log.level_flush, "NONE" ) ) ) config->log.level_flush1 = 8;
     329           0 :   else                                                            config->log.level_flush1 = parse_log_level( config->log.level_flush );
     330           3 :   if( FD_UNLIKELY( -1==config->log.level_logfile1 ) ) FD_LOG_ERR(( "unrecognized [log.level_logfile] `%s`", config->log.level_logfile ));
     331           3 :   if( FD_UNLIKELY( -1==config->log.level_stderr1 ) )  FD_LOG_ERR(( "unrecognized [log.level_stderr] `%s`", config->log.level_stderr ));
     332           3 :   if( FD_UNLIKELY( -1==config->log.level_flush1 ) )   FD_LOG_ERR(( "unrecognized [log.level_flush] `%s`", config->log.level_flush ));
     333             : 
     334           3 :   config->development.core_dump_level = parse_core_dump_level( config->development.core_dump );
     335           3 :   if( FD_UNLIKELY( -1==config->development.core_dump_level ) ) FD_LOG_ERR(( "unrecognized [development.core_dump] `%s`", config->development.core_dump ));
     336             : 
     337           3 :   replace( config->paths.base, "{user}", config->user );
     338           3 :   replace( config->paths.base, "{name}", config->name );
     339             : 
     340           3 :   if( FD_UNLIKELY( !strcmp( config->paths.identity_key, "" ) ) ) {
     341             :     /* Development binaries generate an identity key on boot. */
     342           3 :     if( FD_UNLIKELY( config->is_live_cluster && !dev ) ) FD_LOG_ERR(( "configuration file must specify [consensus.identity_path] when joining a live cluster" ));
     343             : 
     344           3 :     FD_TEST( fd_cstr_printf_check( config->paths.identity_key,
     345           3 :                                    sizeof(config->paths.identity_key),
     346           3 :                                    NULL,
     347           3 :                                    "%s/identity.json",
     348           3 :                                    config->paths.base ) );
     349           3 :   } else {
     350           0 :     replace( config->paths.identity_key, "{user}", config->user );
     351           0 :     replace( config->paths.identity_key, "{name}", config->name );
     352           0 :   }
     353             : 
     354           3 :   replace( config->paths.vote_account, "{user}", config->user );
     355           3 :   replace( config->paths.vote_account, "{name}", config->name );
     356             : 
     357           3 :   if( FD_UNLIKELY( strcmp( config->paths.snapshots, "" ) ) ) {
     358           0 :     replace( config->paths.snapshots, "{user}", config->user );
     359           0 :     replace( config->paths.snapshots, "{name}", config->name );
     360           3 :   } else {
     361           3 :     FD_TEST( fd_cstr_printf_check( config->paths.snapshots, sizeof(config->paths.snapshots), NULL, "%s/snapshots", config->paths.base ) );
     362           3 :   }
     363             : 
     364           3 :   if( FD_UNLIKELY( strcmp( config->paths.genesis, "" ) ) ) {
     365           0 :     replace( config->paths.genesis, "{user}", config->user );
     366           0 :     replace( config->paths.genesis, "{name}", config->name );
     367           3 :   } else {
     368           3 :     FD_TEST( fd_cstr_printf_check( config->paths.genesis, sizeof(config->paths.genesis), NULL, "%s/genesis.bin", config->paths.base ) );
     369           3 :   }
     370             : 
     371           3 :   long ts = -fd_log_wallclock();
     372           3 :   config->is_dev = dev;
     373             : 
     374           3 :   config->tick_per_ns_mu = dev ? fd_tempo_tick_per_ns_dev( &config->tick_per_ns_sigma )
     375           3 :                                : fd_tempo_tick_per_ns    ( &config->tick_per_ns_sigma );
     376           3 :   FD_LOG_INFO(( "calibrating fd_tempo tick_per_ns took %ld ms", (fd_log_wallclock()+ts)/(1000L*1000L) ));
     377             : 
     378           3 :   if( 0!=strcmp( config->net.bind_address, "" ) ) {
     379           0 :     if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->net.bind_address, &config->net.bind_address_parsed ) ) ) {
     380           0 :       FD_LOG_ERR(( "`net.bind_address` is not a valid IPv4 address" ));
     381           0 :     }
     382           0 :   }
     383             : 
     384           3 :   if(      FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "perf"     ) ) ) config->tiles.pack.schedule_strategy_enum = 0;
     385           3 :   else if( FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "balanced" ) ) ) config->tiles.pack.schedule_strategy_enum = 1;
     386           0 :   else if( FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "revenue"  ) ) ) {
     387           0 :     FD_LOG_ERR(( "the revenue scheduler has been removed.  Please update [tiles.pack.schedule_strategy]" ));
     388           0 :   }
     389           0 :   else FD_LOG_ERR(( "[tiles.pack.schedule_strategy] %s not recognized", config->tiles.pack.schedule_strategy ));
     390             : 
     391           3 :   fd_config_fill_net( config );
     392             : 
     393           3 :   if( FD_UNLIKELY( config->is_firedancer ) ) {
     394           3 :     fd_config_fillf( config );
     395           3 :   } else {
     396           0 :     fd_config_fillh( config );
     397           0 :   }
     398             : 
     399           3 :   fd_config_auto( config );
     400           3 :   if( FD_UNLIKELY( !strcmp( config->net.provider, "auto" ) ) ) {
     401           0 :     FD_LOG_ERR(( "failed to resolve automatic network provider" ));
     402           0 :   }
     403           3 :   fd_config_validate( config );
     404             : 
     405           3 :   config->limits.max_cost_per_block   = fd_ulong_if( !!config->development.bench.max_cost_per_block,   config->development.bench.max_cost_per_block,   FD_PACK_MAX_COST_PER_BLOCK_UPPER_BOUND );
     406           3 :   config->limits.max_shreds_per_block = fd_ulong_if( !!config->development.bench.max_shreds_per_block, config->development.bench.max_shreds_per_block, FD_SHRED_BLK_MAX );
     407           3 :   config->limits.max_txn_per_slot     = fd_ulong_min( config->limits.max_cost_per_block/FD_PACK_MIN_TXN_COST,
     408           3 :                                                       (config->limits.max_shreds_per_block*FD_SHRED_DATA_PAYLOAD_MAX - 65UL*sizeof(fd_entry_batch_header_t))/FD_TXN_MIN_SERIALIZED_SZ ); /* 64 ticks + one giant microblock */
     409           3 :   if( FD_LIKELY( !config->development.bench.max_cost_per_block && !config->development.bench.max_shreds_per_block ) ) FD_TEST( config->limits.max_txn_per_slot==FD_MAX_TXN_PER_SLOT );
     410             : 
     411           3 :   if( FD_LIKELY( config->is_live_cluster) ) {
     412           0 :     if( FD_UNLIKELY( !config->development.sandbox ) )                            FD_LOG_ERR(( "trying to join a live cluster, but configuration disables the sandbox which is a development only feature" ));
     413           0 :     if( FD_UNLIKELY( config->development.no_clone ) )                            FD_LOG_ERR(( "trying to join a live cluster, but configuration disables multiprocess which is a development only feature" ));
     414           0 :     if( FD_UNLIKELY( config->development.bench.max_cost_per_block ) )            FD_LOG_ERR(( "trying to join a live cluster, but configuration sets [development.bench.max_cost_per_block] which is a development only feature" ));
     415           0 :     if( FD_UNLIKELY( config->development.bench.max_shreds_per_block ) )          FD_LOG_ERR(( "trying to join a live cluster, but configuration sets [development.bench.max_shreds_per_block] which is a development only feature" ));
     416           0 :     if( FD_UNLIKELY( config->development.bench.disable_blockstore_from_slot ) )  FD_LOG_ERR(( "trying to join a live cluster, but configuration has a non-zero value for [development.bench.disable_blockstore_from_slot] which is a development only feature" ));
     417           0 :     if( FD_UNLIKELY( config->development.bench.disable_status_cache ) )          FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.bench.disable_status_cache] which is a development only feature" ));
     418           0 :   }
     419             : 
     420             :   /* When running a local cluster, some options are overridden by default
     421             :      to make starting and running in development environments a little
     422             :      easier and less strict. */
     423           3 :   if( FD_UNLIKELY( is_local_cluster ) ) {
     424           0 :     if( FD_LIKELY( !strcmp( config->paths.vote_account, "" ) ) ) {
     425           0 :       FD_TEST( fd_cstr_printf_check( config->paths.vote_account,
     426           0 :                                      sizeof( config->paths.vote_account ),
     427           0 :                                      NULL,
     428           0 :                                      "%s/vote-account.json",
     429           0 :                                      config->paths.base ) );
     430           0 :     }
     431             : 
     432           0 :     strncpy( config->cluster, "development", sizeof(config->cluster) );
     433             : 
     434           0 :     if( FD_UNLIKELY( !config->is_firedancer ) ) {
     435             :       /* By default only_known is true for validators to ensure secure
     436             :         snapshot download, but in development it doesn't matter and
     437             :         often the developer does not provide known peers. */
     438           0 :       config->frankendancer.rpc.only_known = 0;
     439             : 
     440             :       /* When starting from a new genesis block, this needs to be off else
     441             :         the validator will get stuck forever. */
     442           0 :       config->frankendancer.consensus.wait_for_vote_to_start_leader = 0;
     443             : 
     444             :       /* We have to wait until we get a snapshot before we can join a
     445             :         second validator to this one, so make this smaller than the
     446             :         default.  */
     447           0 :       config->frankendancer.snapshots.full_snapshot_interval_slots = fd_uint_min( config->frankendancer.snapshots.full_snapshot_interval_slots, 200U );
     448           0 :     }
     449           0 :   }
     450             : 
     451           3 :   if( FD_UNLIKELY( config->is_firedancer && strcmp( config->firedancer.consensus.wait_for_supermajority_with_bank_hash, "" ) && (!config->consensus.expected_shred_version || config->consensus.wait_for_vote_to_start_leader) ) ) {
     452           0 :     FD_LOG_ERR(( "Config option [consensus.wait_for_supermajority_with_bank_hash] requires consensus.expected_shred_version!=0 and consensus.wait_for_vote_to_start_leader==false." ));
     453           0 :   }
     454             : 
     455           3 : }
     456             : 
     457         282 : #define CFG_HAS_NON_EMPTY( key ) do {                  \
     458         282 :   if( !strnlen( config->key, sizeof(config->key) ) ) { \
     459           0 :     FD_LOG_ERR(( "missing `%s`", #key ));              \
     460           0 :   }                                                    \
     461         282 : } while(0)
     462             : 
     463         636 : #define CFG_HAS_NON_ZERO( key ) do {                           \
     464         636 :   if( !config->key ) { FD_LOG_ERR(( "missing `%s`", #key )); } \
     465         636 : } while(0)
     466             : 
     467          24 : #define CFG_HAS_POW2( key ) do {                       \
     468          24 :   ulong value = (ulong)( config -> key );              \
     469          24 :   if( !value || !fd_ulong_is_pow2( value ) ) {         \
     470           0 :     FD_LOG_ERR(( "`%s` must be a power of 2", #key )); \
     471           0 :   }                                                    \
     472          24 : } while(0)
     473             : 
     474             : static void
     475           9 : fd_config_validatef( fd_configf_t const * config ) {
     476           9 :   CFG_HAS_NON_ZERO( layout.sign_tile_count );
     477           9 :   CFG_HAS_NON_ZERO( layout.resolv_tile_count );
     478           9 :   CFG_HAS_NON_ZERO( layout.execle_tile_count );
     479           9 :   CFG_HAS_NON_ZERO( layout.snapzp_tile_count );
     480           9 :   CFG_HAS_NON_ZERO( layout.snapsv_tile_count );
     481           9 :   CFG_HAS_NON_ZERO( layout.snapsv_io_worker_count );
     482           9 :   CFG_HAS_NON_ZERO( layout.snapdc_tile_count );
     483           9 :   if( FD_UNLIKELY( config->layout.sign_tile_count < 2 ) ) {
     484           0 :     FD_LOG_ERR(( "layout.sign_tile_count must be >= 2" ));
     485           0 :   }
     486             : 
     487           9 :   if( FD_UNLIKELY( config->snapshots.sources.gossip.allow_any && config->snapshots.sources.gossip.allow_list_cnt>0UL ) ) {
     488           0 :     FD_LOG_ERR(( "`snapshots.sources.gossip` has an explicit list of %lu allowed peer(s) in `allow_list` "
     489           0 :                  "but also allows any peer with `allow_any=true`. `allow_list` has no effect and may "
     490           0 :                  "give a false sense of security. Please modify one of the two options and restart.",
     491           0 :                  config->snapshots.sources.gossip.allow_list_cnt ));
     492           0 :   }
     493           9 :   for( ulong i=0UL; i<config->snapshots.sources.gossip.allow_list_cnt; i++ ) {
     494           0 :     for( ulong j=0UL; j<config->snapshots.sources.gossip.block_list_cnt; j++ ) {
     495           0 :       if( FD_UNLIKELY( 0==strcmp( config->snapshots.sources.gossip.allow_list[ i ], config->snapshots.sources.gossip.block_list[ j ] ) ) ) {
     496           0 :         FD_LOG_ERR(( "`snapshots.sources.gossip` has repeated public key `%s` in both allow[%lu] and block[%lu] lists.  "
     497           0 :                      "Please modify one of the two options and restart.", config->snapshots.sources.gossip.allow_list[ i ], i, j ));
     498             : 
     499           0 :       }
     500           0 :     }
     501           0 :   }
     502             : 
     503           9 :   if( FD_UNLIKELY( config->snapshots.server.enabled && !config->layout.enable_snapshot_production ) ) {
     504           0 :     FD_LOG_ERR(( "[snapshots.server].enabled=true requires [layout].enable_snapshot_production=true" ));
     505           0 :   }
     506             : 
     507           9 :   CFG_HAS_NON_ZERO( snapshots.wait_for_peers_timeout_seconds );
     508           9 :   if( FD_UNLIKELY( config->snapshots.server.idle_timeout_millis<100UL ||
     509           9 :                    config->snapshots.server.idle_timeout_millis>=60000UL ) ) {
     510           0 :     FD_LOG_ERR(( "`snapshots.server.idle_timeout_millis` must be in [100,60000)" ));
     511           0 :   }
     512           9 :   if( FD_UNLIKELY( config->snapshots.server.send_timeout_millis<100UL ||
     513           9 :                    config->snapshots.server.send_timeout_millis>=60000UL ) ) {
     514           0 :     FD_LOG_ERR(( "`snapshots.server.send_timeout_millis` must be in [100,60000)" ));
     515           0 :   }
     516             : 
     517           9 :   if( FD_UNLIKELY( config->snapshots.max_full_snapshots_to_keep>FD_SSARCHIVE_MAX_ENTRIES ) ) {
     518           0 :     FD_LOG_ERR(( "`snapshots.max_full_snapshots_to_keep` is %u but must be at most %lu",
     519           0 :                  config->snapshots.max_full_snapshots_to_keep, FD_SSARCHIVE_MAX_ENTRIES ));
     520           0 :   }
     521           9 :   if( FD_UNLIKELY( config->snapshots.max_incremental_snapshots_to_keep>FD_SSARCHIVE_MAX_ENTRIES ) ) {
     522           0 :     FD_LOG_ERR(( "`snapshots.max_incremental_snapshots_to_keep` is %u but must be at most %lu",
     523           0 :                  config->snapshots.max_incremental_snapshots_to_keep, FD_SSARCHIVE_MAX_ENTRIES ));
     524           0 :   }
     525             : 
     526           9 :   CFG_HAS_NON_ZERO( accounts.max_accounts   );
     527           9 :   CFG_HAS_NON_ZERO( accounts.cache_size_gib );
     528             : 
     529           9 :   CFG_HAS_NON_ZERO( development.genesis.max_file_size_mib );
     530           9 :   if( FD_UNLIKELY( config->development.genesis.max_file_size_mib>FD_GENESIS_MAX_FILE_SIZE_MIB ) ) {
     531           3 :     FD_LOG_ERR(( "`development.genesis.max_file_size_mib` must be at most %lu", FD_GENESIS_MAX_FILE_SIZE_MIB ));
     532           3 :   }
     533             : 
     534           6 :   CFG_HAS_NON_ZERO( runtime.program_cache.mean_cache_entry_size );
     535           6 :   CFG_HAS_NON_ZERO( runtime.program_cache.heap_size_mib );
     536           6 :   if( config->runtime.program_cache.mean_cache_entry_size < 4096 ) { FD_LOG_ERR(( "`%s` must be >= 4096", "runtime.program_cache.mean_cache_entry_size" )); }
     537           6 :   if( config->runtime.program_cache.heap_size_mib < 32 ) { FD_LOG_ERR(( "`%s` must be >= 32", "runtime.program_cache.heap_size_mib" )); }
     538           6 : }
     539             : 
     540             : static void
     541          12 : fd_config_validateh( fd_configh_t const * config ) {
     542          12 :   CFG_HAS_NON_EMPTY( dynamic_port_range );
     543             : 
     544          12 :   CFG_HAS_NON_EMPTY( ledger.snapshot_archive_format );
     545             : 
     546          12 :   CFG_HAS_NON_ZERO( snapshots.full_snapshot_interval_slots );
     547          12 :   CFG_HAS_NON_ZERO( snapshots.incremental_snapshot_interval_slots );
     548          12 :   CFG_HAS_NON_ZERO( snapshots.minimum_snapshot_download_speed );
     549          12 :   CFG_HAS_NON_ZERO( snapshots.maximum_snapshot_download_abort );
     550             : 
     551          12 :   CFG_HAS_NON_EMPTY( layout.agave_affinity );
     552          12 :   CFG_HAS_NON_ZERO ( layout.resolh_tile_count );
     553          12 :   CFG_HAS_NON_ZERO ( layout.bank_tile_count );
     554          12 : }
     555             : 
     556             : void
     557          21 : fd_config_validate( fd_config_t const * config ) {
     558          21 :   if( FD_LIKELY( config->is_firedancer ) ) {
     559           9 :     fd_config_validatef( &config->firedancer );
     560          12 :   } else {
     561          12 :     fd_config_validateh( &config->frankendancer );
     562          12 :   }
     563             : 
     564          21 :   CFG_HAS_NON_EMPTY( name );
     565          21 :   CFG_HAS_NON_EMPTY( paths.base );
     566             : 
     567          21 :   CFG_HAS_NON_EMPTY( log.colorize );
     568          21 :   CFG_HAS_NON_EMPTY( log.level_logfile );
     569          21 :   CFG_HAS_NON_EMPTY( log.level_stderr );
     570          21 :   CFG_HAS_NON_EMPTY( log.level_flush );
     571             : 
     572          21 :   CFG_HAS_NON_EMPTY( layout.affinity );
     573          21 :   CFG_HAS_NON_EMPTY( layout.blocklist_cores );
     574          21 :   CFG_HAS_NON_ZERO ( layout.net_tile_count );
     575          21 :   CFG_HAS_NON_ZERO ( layout.quic_tile_count );
     576          21 :   CFG_HAS_NON_ZERO ( layout.verify_tile_count );
     577          21 :   CFG_HAS_NON_ZERO ( layout.shred_tile_count );
     578             : 
     579          21 :   CFG_HAS_NON_EMPTY( hugetlbfs.mount_path );
     580          21 :   CFG_HAS_NON_EMPTY( hugetlbfs.max_page_size );
     581             : 
     582          21 :   CFG_HAS_NON_ZERO( net.ingress_buffer_size );
     583             : 
     584          21 :   ulong bench_cost   = config->development.bench.max_cost_per_block;
     585          21 :   ulong bench_shreds = config->development.bench.max_shreds_per_block;
     586          21 :   if( FD_UNLIKELY( bench_cost && (bench_cost<FD_PACK_MAX_COST_PER_BLOCK_UPPER_BOUND || bench_cost>=UINT_MAX) ) ) /* fd_pack_limits_t: [0,UINT_MAX) */
     587           0 :     FD_LOG_ERR(( "invalid [development.bench.max_cost_per_block]: must be 0 or in [%lu,%u)", FD_PACK_MAX_COST_PER_BLOCK_UPPER_BOUND, UINT_MAX ));
     588          21 :   if( FD_UNLIKELY( bench_shreds && (bench_shreds%FD_SHRED_BLK_MAX || bench_shreds>FD_SHRED_BLK_MAX_RAISED) ) )
     589           0 :     FD_LOG_ERR(( "invalid [development.bench.max_shreds_per_block]: must be 0 or a multiple of %lu up to %lu", (ulong)FD_SHRED_BLK_MAX, FD_SHRED_BLK_MAX_RAISED ));
     590             :   /* Frankendancer's Agave side only knows the booleans behind these
     591             :      (18x block cost, 32x shreds), so it accepts exactly the values that
     592             :      match what the Firedancer side used to hardcode for them. */
     593          21 :   if( FD_UNLIKELY( !config->is_firedancer && bench_cost && bench_cost!=18UL*FD_PACK_MAX_COST_PER_BLOCK_LOWER_BOUND ) )
     594           0 :     FD_LOG_ERR(( "invalid [development.bench.max_cost_per_block]: Frankendancer supports 0 or %lu", 18UL*FD_PACK_MAX_COST_PER_BLOCK_LOWER_BOUND ));
     595          21 :   if( FD_UNLIKELY( !config->is_firedancer && bench_shreds && bench_shreds!=32UL*FD_SHRED_BLK_MAX ) )
     596           0 :     FD_LOG_ERR(( "invalid [development.bench.max_shreds_per_block]: Frankendancer supports 0 or %lu", 32UL*FD_SHRED_BLK_MAX ));
     597             : 
     598          21 :   if( 0==strcmp( config->net.provider, "xdp" ) ) {
     599           9 :     if( 0!=strcmp( config->net.xdp.xdp_mode, "skb"     ) &&
     600           9 :         0!=strcmp( config->net.xdp.xdp_mode, "drv"     ) &&
     601           9 :         0!=strcmp( config->net.xdp.xdp_mode, "auto"    ) &&
     602           9 :         0!=strcmp( config->net.xdp.xdp_mode, "default" ) ) {
     603           0 :       FD_LOG_ERR(( "invalid `net.xdp.xdp_mode`: \"%s\"; must be \"skb\", \"drv\", \"auto\" or \"default\"",
     604           0 :                    config->net.xdp.xdp_mode ));
     605           0 :     }
     606             : 
     607           9 :     if( 0!=strcmp( config->net.xdp.poll_mode, "prefbusy" ) &&
     608           9 :         0!=strcmp( config->net.xdp.poll_mode, "softirq"  ) &&
     609           9 :         0!=strcmp( config->net.xdp.poll_mode, "auto"     ) ) {
     610           0 :       FD_LOG_ERR(( "invalid `net.xdp.poll_mode`: \"%s\"; must be \"prefbusy\", \"softirq\" or \"auto\"",
     611           0 :                    config->net.xdp.poll_mode ));
     612           0 :     }
     613             : 
     614           9 :     CFG_HAS_POW2     ( net.xdp.xdp_rx_queue_size );
     615           9 :     CFG_HAS_POW2     ( net.xdp.xdp_tx_queue_size );
     616           9 :     if( 0!=strcmp( config->net.xdp.rss_queue_mode, "dedicated" ) &&
     617           9 :         0!=strcmp( config->net.xdp.rss_queue_mode, "simple"    ) &&
     618           9 :         0!=strcmp( config->net.xdp.rss_queue_mode, "auto"      ) ) {
     619           0 :       FD_LOG_ERR(( "invalid `net.xdp.rss_queue_mode`: \"%s\"; must be \"simple\", \"dedicated\" or \"auto\"",
     620           0 :                    config->net.xdp.rss_queue_mode ));
     621           0 :     }
     622          12 :   } else if( 0==strcmp( config->net.provider, "mlx5" ) ) {
     623           0 :     CFG_HAS_POW2( net.mlx5.rx_queue_size );
     624           0 :     CFG_HAS_POW2( net.mlx5.tx_queue_size );
     625           0 :     if( FD_UNLIKELY( config->net.mlx5.rx_queue_size<=FD_MLX5_BATCH_SIZE ||
     626           0 :                      config->net.mlx5.tx_queue_size< FD_MLX5_BATCH_SIZE ) ) {
     627           0 :       FD_LOG_ERR(( "invalid mlx5 queue depth: RX must exceed %u and TX must be at least %u",
     628           0 :                    FD_MLX5_BATCH_SIZE, FD_MLX5_BATCH_SIZE ));
     629           0 :     }
     630           0 :     if( FD_UNLIKELY( config->net.mlx5.rx_queue_size>FD_MLX5_QUEUE_DEPTH_MAX ||
     631           0 :                      config->net.mlx5.tx_queue_size>FD_MLX5_QUEUE_DEPTH_MAX ) ) {
     632           0 :       FD_LOG_ERR(( "invalid mlx5 queue depth: RX and TX must not exceed %u", FD_MLX5_QUEUE_DEPTH_MAX ));
     633           0 :     }
     634          12 :   } else if( 0==strcmp( config->net.provider, "socket" ) ) {
     635           0 :     CFG_HAS_NON_ZERO( net.socket.receive_buffer_size );
     636           0 :     CFG_HAS_NON_ZERO( net.socket.send_buffer_size );
     637          12 :   } else if( 0==strcmp( config->net.provider, "auto" ) ) {
     638             :     /* "auto" is resolved after interface discovery in fd_config_fill(). */
     639           9 :   } else {
     640           3 :     FD_LOG_ERR(( "invalid `net.provider`: \"%s\"; must be \"auto\", \"xdp\", \"socket\" or \"mlx5\"",
     641           3 :                  config->net.provider ));
     642           3 :   }
     643             : 
     644          18 :   CFG_HAS_NON_ZERO( tiles.netlink.max_routes           );
     645          18 :   CFG_HAS_NON_ZERO( tiles.netlink.max_peer_routes      );
     646          18 :   CFG_HAS_NON_ZERO( tiles.netlink.max_neighbors        );
     647             : 
     648          18 :   CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_connections );
     649          18 :   CFG_HAS_NON_ZERO( tiles.quic.txn_reassembly_count );
     650          18 :   CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_handshakes );
     651          18 :   CFG_HAS_NON_ZERO( tiles.quic.idle_timeout_millis );
     652             : 
     653          18 :   CFG_HAS_NON_ZERO( tiles.verify.signature_cache_size );
     654          18 :   CFG_HAS_NON_ZERO( tiles.verify.receive_buffer_size );
     655             : 
     656          18 :   CFG_HAS_NON_ZERO( tiles.dedup.signature_cache_size );
     657             : 
     658          18 :   CFG_HAS_NON_ZERO( tiles.pack.max_pending_transactions );
     659             : 
     660          18 :   CFG_HAS_NON_ZERO( tiles.shred.max_pending_shred_sets );
     661             : 
     662          18 :   if( config->is_firedancer ) {
     663           6 :     CFG_HAS_POW2( tiles.repair.slot_max );
     664           6 :     CFG_HAS_NON_ZERO( tiles.rotor.slot_max );
     665           6 :   }
     666             : 
     667          18 :   if( FD_UNLIKELY( config->tiles.bundle.keepalive_interval_millis <    3000 ||
     668          18 :                    config->tiles.bundle.keepalive_interval_millis > 3600000 ) ) {
     669           0 :     FD_LOG_ERR(( "`tiles.bundle.keepalive_interval_millis` must be in range [3000, 3,600,000]" ));
     670           0 :   }
     671             : 
     672          18 :   CFG_HAS_NON_EMPTY( development.core_dump );
     673             : 
     674          18 :   CFG_HAS_NON_ZERO( development.genesis.target_tick_duration_micros );
     675          18 :   CFG_HAS_NON_ZERO( development.genesis.ticks_per_slot );
     676          18 :   CFG_HAS_NON_ZERO( development.genesis.fund_initial_accounts );
     677          18 :   CFG_HAS_NON_ZERO( development.genesis.fund_initial_amount_lamports );
     678             : 
     679          18 :   CFG_HAS_NON_ZERO ( development.bench.benchg_tile_count );
     680          18 :   CFG_HAS_NON_ZERO ( development.bench.benchs_tile_count );
     681          18 :   CFG_HAS_NON_EMPTY( development.bench.affinity );
     682             : 
     683          18 :   CFG_HAS_NON_ZERO( development.bundle.ssl_heap_size_mib );
     684          18 : }
     685             : 
     686             : #undef CFG_HAS_NON_EMPTY
     687             : #undef CFG_HAS_NON_ZERO
     688             : #undef CFG_HAS_POW2
     689             : 
     690             : void
     691             : fd_config_load( int           is_firedancer,
     692             :                 int           is_local_cluster,
     693             :                 char const *  default_config,
     694             :                 ulong         default_config_sz,
     695             :                 char const *  override_config,
     696             :                 char const *  override_config_path,
     697             :                 ulong         override_config_sz,
     698             :                 char const *  user_config,
     699             :                 ulong         user_config_sz,
     700             :                 char const *  user_config_path,
     701             :                 fd_config_t * config,
     702           3 :                 int           dev ) {
     703           3 :   memset( config, 0, sizeof(config_t) );
     704           3 :   config->is_firedancer = is_firedancer;
     705           3 :   config->boot_timestamp_nanos = fd_log_wallclock();
     706             : 
     707           3 :   fd_config_load_buf( config, default_config, default_config_sz, "default.toml" );
     708           3 :   fd_config_validate( config );
     709           3 :   if( FD_UNLIKELY( override_config ) ) {
     710           0 :     fd_config_load_buf( config, override_config, override_config_sz, override_config_path );
     711           0 :     fd_config_validate( config );
     712           0 :   }
     713           3 :   if( FD_LIKELY( user_config ) ) {
     714           0 :     fd_config_load_buf( config, user_config, user_config_sz, user_config_path );
     715           0 :     fd_config_validate( config );
     716           0 :     if( FD_UNLIKELY( user_config_sz>sizeof(config->user_config)-1UL ) ) {
     717           0 :       if( FD_UNLIKELY( config->telemetry && config->tiles.event.url[ 0 ] ) ) {
     718           0 :         FD_LOG_ERR(( "config file (%s) is too large (%lu bytes, max %lu) to report in telemetry", user_config_path, user_config_sz, sizeof(config->user_config)-1UL ));
     719           0 :       }
     720           0 :     } else {
     721           0 :       fd_memcpy( config->user_config, user_config, user_config_sz );
     722           0 :       config->user_config[ user_config_sz ] = '\0';
     723           0 :       config->user_config_len = user_config_sz;
     724           0 :     }
     725           0 :   }
     726             : 
     727           3 :   fd_config_fill( config, is_local_cluster, dev );
     728           3 : }
     729             : 
     730             : int
     731           0 : fd_config_to_memfd( fd_config_t const * config ) {
     732           0 :   int config_memfd = memfd_create( "fd_config", 0 );
     733           0 :   if( FD_UNLIKELY( -1==config_memfd ) ) return -1;
     734           0 :   if( FD_UNLIKELY( -1==ftruncate( config_memfd, sizeof( config_t ) ) ) ) {
     735           0 :     if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_WARNING(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     736           0 :     return -1;
     737           0 :   }
     738             : 
     739           0 :   uchar * bytes = mmap( NULL, sizeof( config_t ), PROT_READ|PROT_WRITE, MAP_SHARED, config_memfd, 0 );
     740           0 :   if( FD_UNLIKELY( bytes==MAP_FAILED ) ) {
     741           0 :     if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_WARNING(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     742           0 :     return -1;
     743           0 :   }
     744             : 
     745           0 :   fd_memcpy( bytes, config, sizeof( config_t ) );
     746           0 :   if( FD_UNLIKELY( munmap( bytes, sizeof( config_t ) ) ) ) {
     747           0 :     FD_LOG_WARNING(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     748           0 :     return -1;
     749           0 :   }
     750             : 
     751           0 :   return config_memfd;
     752           0 : }

Generated by: LCOV version 1.14