LCOV - code coverage report
Current view: top level - app/shared - fd_config.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 373 604 61.8 %
Date: 2026-11-09 04:27:00 Functions: 12 13 92.3 %

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

Generated by: LCOV version 1.14