LCOV - code coverage report
Current view: top level - app/shared - fd_config.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 303 475 63.8 %
Date: 2025-09-17 04:38:03 Functions: 11 12 91.7 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "fd_config.h"
       3             : #include "fd_config_private.h"
       4             : 
       5             : #include "../platform/fd_net_util.h"
       6             : #include "../platform/fd_sys_util.h"
       7             : #include "../../ballet/toml/fd_toml.h"
       8             : #include "../../disco/genesis/fd_genesis_cluster.h"
       9             : 
      10             : #include <unistd.h>
      11             : #include <errno.h>
      12             : #include <stdlib.h> /* strtoul */
      13             : #include <sys/utsname.h>
      14             : #include <sys/mman.h>
      15             : 
      16             : /* TODO: Rewrite this ... */
      17             : 
      18             : static inline void
      19             : replace( char *       in,
      20             :          const char * pat,
      21          36 :          const char * sub ) {
      22          36 :   char * replace = strstr( in, pat );
      23          36 :   if( FD_LIKELY( replace ) ) {
      24          12 :     ulong pat_len = strlen( pat );
      25          12 :     ulong sub_len = strlen( sub );
      26          12 :     ulong in_len  = strlen( in );
      27          12 :     if( FD_UNLIKELY( pat_len > in_len ) ) return;
      28             : 
      29          12 :     ulong total_len = in_len - pat_len + sub_len;
      30          12 :     if( FD_UNLIKELY( total_len >= PATH_MAX ) )
      31           0 :       FD_LOG_ERR(( "configuration scratch directory path too long: `%s`", in ));
      32             : 
      33          12 :     uchar after[PATH_MAX] = {0};
      34          12 :     fd_memcpy( after, replace + pat_len, strlen( replace + pat_len ) );
      35          12 :     fd_memcpy( replace, sub, sub_len );
      36          12 :     ulong after_len = strlen( ( const char * ) after );
      37          12 :     fd_memcpy( replace + sub_len, after, after_len );
      38          12 :     in[ total_len ] = '\0';
      39          12 :   }
      40          36 : }
      41             : 
      42             : 
      43             : FD_FN_CONST static inline int
      44          18 : parse_log_level( char const * level ) {
      45          18 :   if( FD_UNLIKELY( !strcmp( level, "DEBUG" ) ) )    return 0;
      46          18 :   if( FD_UNLIKELY( !strcmp( level, "INFO"  ) ) )    return 1;
      47          12 :   if( FD_UNLIKELY( !strcmp( level, "NOTICE"  ) ) )  return 2;
      48           6 :   if( FD_UNLIKELY( !strcmp( level, "WARNING"  ) ) ) return 3;
      49           0 :   if( FD_UNLIKELY( !strcmp( level, "ERR" ) ) )      return 4;
      50           0 :   if( FD_UNLIKELY( !strcmp( level, "CRIT" ) ) )     return 5;
      51           0 :   if( FD_UNLIKELY( !strcmp( level, "ALERT" ) ) )    return 6;
      52           0 :   if( FD_UNLIKELY( !strcmp( level, "EMERG" ) ) )    return 7;
      53           0 :   return -1;
      54           0 : }
      55             : 
      56             : void
      57             : fd_config_load_buf( fd_config_t * out,
      58             :                     char const *  buf,
      59             :                     ulong         sz,
      60           6 :                     char const *  path ) {
      61           6 :   static uchar pod_mem[ 1UL<<26 ];
      62           6 :   uchar * pod = fd_pod_join( fd_pod_new( pod_mem, sizeof(pod_mem) ) );
      63             : 
      64           6 :   fd_toml_err_info_t toml_err[1];
      65           6 :   uchar scratch[ 4096 ];
      66           6 :   int toml_errc = fd_toml_parse( buf, sz, pod, scratch, sizeof(scratch), toml_err );
      67           6 :   if( FD_UNLIKELY( toml_errc!=FD_TOML_SUCCESS ) ) {
      68           0 :     switch( toml_errc ) {
      69           0 :     case FD_TOML_ERR_POD:
      70           0 :       FD_LOG_ERR(( "Failed to parse config file (%s): ran out of buffer space while parsing", path ));
      71           0 :       break;
      72           0 :     case FD_TOML_ERR_SCRATCH:
      73           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: ran out of scratch space while parsing", path, toml_err->line ));
      74           0 :       break;
      75           0 :     case FD_TOML_ERR_KEY:
      76           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: oversize key", path, toml_err->line ));
      77           0 :       break;
      78           0 :     case FD_TOML_ERR_DUP:
      79           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: duplicate key", path, toml_err->line ));
      80           0 :       break;
      81           0 :     case FD_TOML_ERR_RANGE:
      82           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: invalid value for key", path, toml_err->line ));
      83           0 :       break;
      84           0 :     case FD_TOML_ERR_PARSE:
      85           0 :       FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu", path, toml_err->line ));
      86           0 :       break;
      87           0 :     default:
      88           0 :       FD_LOG_ERR(( "Failed to parse config file (%s): %s", path, fd_toml_strerror( toml_errc ) ));
      89           0 :       break;
      90           0 :     }
      91           0 :   }
      92             : 
      93           6 :   fd_config_extract_pod( pod, out );
      94             : 
      95           6 :   fd_pod_delete( fd_pod_leave( pod ) );
      96           6 : }
      97             : 
      98             : static void
      99           3 : fd_config_fillf( fd_config_t * config ) {
     100           3 :   (void)config;
     101           3 : }
     102             : 
     103             : static void
     104           3 : fd_config_fillh( fd_config_t * config ) {
     105           3 :   if( FD_UNLIKELY( strcmp( config->frankendancer.paths.accounts_path, "" ) ) ) {
     106           0 :     replace( config->frankendancer.paths.accounts_path, "{user}", config->user );
     107           0 :     replace( config->frankendancer.paths.accounts_path, "{name}", config->name );
     108           0 :   }
     109             : 
     110           3 :   if( FD_UNLIKELY( strcmp( config->frankendancer.snapshots.path, "" ) ) ) {
     111           0 :     replace( config->frankendancer.snapshots.path, "{user}", config->user );
     112           0 :     replace( config->frankendancer.snapshots.path, "{name}", config->name );
     113           3 :   } else {
     114           3 :     strncpy( config->frankendancer.snapshots.path, config->paths.ledger, sizeof(config->frankendancer.snapshots.path) );
     115           3 :   }
     116             : 
     117           3 :   for( ulong i=0UL; i<config->frankendancer.paths.authorized_voter_paths_cnt; i++ ) {
     118           0 :     replace( config->frankendancer.paths.authorized_voter_paths[ i ], "{user}", config->user );
     119           0 :     replace( config->frankendancer.paths.authorized_voter_paths[ i ], "{name}", config->name );
     120           0 :   }
     121             : 
     122           3 :   if( FD_UNLIKELY( config->tiles.quic.quic_transaction_listen_port!=config->tiles.quic.regular_transaction_listen_port+6 ) )
     123           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.quic_transaction_listen_port] `%hu`. "
     124           3 :                  "This must be 6 more than [tiles.quic.regular_transaction_listen_port] `%hu`",
     125           3 :                  config->tiles.quic.quic_transaction_listen_port,
     126           3 :                  config->tiles.quic.regular_transaction_listen_port ));
     127             : 
     128           3 :   char dynamic_port_range[ 32 ];
     129           3 :   fd_memcpy( dynamic_port_range, config->frankendancer.dynamic_port_range, sizeof(dynamic_port_range) );
     130             : 
     131           3 :   char * dash = strstr( dynamic_port_range, "-" );
     132           3 :   if( FD_UNLIKELY( !dash ) )
     133           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     134           3 :                  "This must be formatted like `<min>-<max>`",
     135           3 :                  config->frankendancer.dynamic_port_range ));
     136             : 
     137           3 :   *dash = '\0';
     138           3 :   char * endptr;
     139           3 :   ulong agave_port_min = strtoul( dynamic_port_range, &endptr, 10 );
     140           3 :   if( FD_UNLIKELY( *endptr != '\0' || agave_port_min > USHORT_MAX ) )
     141           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     142           3 :                  "This must be formatted like `<min>-<max>`",
     143           3 :                  config->frankendancer.dynamic_port_range ));
     144           3 :   ulong agave_port_max = strtoul( dash + 1, &endptr, 10 );
     145           3 :   if( FD_UNLIKELY( *endptr != '\0' || agave_port_max > USHORT_MAX ) )
     146           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     147           3 :                  "This must be formatted like `<min>-<max>`",
     148           3 :                  config->frankendancer.dynamic_port_range ));
     149           3 :   if( FD_UNLIKELY( agave_port_min > agave_port_max ) )
     150           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     151           3 :                  "The minimum port must be less than or equal to the maximum port",
     152           3 :                  config->frankendancer.dynamic_port_range ));
     153             : 
     154           3 :   if( FD_UNLIKELY( config->tiles.quic.regular_transaction_listen_port >= agave_port_min &&
     155           3 :                    config->tiles.quic.regular_transaction_listen_port < agave_port_max ) )
     156           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.transaction_listen_port] `%hu`. "
     157           3 :                  "This must be outside the dynamic port range `%s`",
     158           3 :                  config->tiles.quic.regular_transaction_listen_port,
     159           3 :                  config->frankendancer.dynamic_port_range ));
     160             : 
     161           3 :   if( FD_UNLIKELY( config->tiles.quic.quic_transaction_listen_port >= agave_port_min &&
     162           3 :                    config->tiles.quic.quic_transaction_listen_port < agave_port_max ) )
     163           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.quic_transaction_listen_port] `%hu`. "
     164           3 :                  "This must be outside the dynamic port range `%s`",
     165           3 :                  config->tiles.quic.quic_transaction_listen_port,
     166           3 :                  config->frankendancer.dynamic_port_range ));
     167             : 
     168           3 :   if( FD_UNLIKELY( config->tiles.shred.shred_listen_port >= agave_port_min &&
     169           3 :                    config->tiles.shred.shred_listen_port < agave_port_max ) )
     170           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.shred.shred_listen_port] `%hu`. "
     171           3 :                  "This must be outside the dynamic port range `%s`",
     172           3 :                  config->tiles.shred.shred_listen_port,
     173           3 :                  config->frankendancer.dynamic_port_range ));
     174           3 : }
     175             : 
     176             : static void
     177           6 : fd_config_fill_net( fd_config_t * config ) {
     178           6 :   if( FD_UNLIKELY( !strcmp( config->net.interface, "" ) && !config->development.netns.enabled ) ) {
     179           6 :     uint ifindex;
     180           6 :     int result = fd_net_util_internet_ifindex( &ifindex );
     181           6 :     if( FD_UNLIKELY( -1==result && errno!=ENODEV ) ) FD_LOG_ERR(( "could not get network device index (%i-%s)", errno, fd_io_strerror( errno ) ));
     182           6 :     else if( FD_UNLIKELY( -1==result ) )
     183           0 :       FD_LOG_ERR(( "no network device found which routes to 8.8.8.8. If no network "
     184           6 :                    "interface is specified in the configuration file, Firedancer "
     185           6 :                    "tries to use the first network interface found which routes to "
     186           6 :                    "8.8.8.8. You can see what this is by running `ip route get 8.8.8.8` "
     187           6 :                    "You can fix this error by specifying a network interface to bind to in "
     188           6 :                    "your configuration file under [net.interface]" ));
     189             : 
     190           6 :     if( FD_UNLIKELY( !if_indextoname( ifindex, config->net.interface ) ) )
     191           0 :       FD_LOG_ERR(( "could not get name of interface with index %u", ifindex ));
     192           6 :   }
     193             : 
     194           6 :   if( FD_UNLIKELY( config->development.netns.enabled ) ) {
     195           0 :     if( !strcmp( config->net.interface, "" ) ) {
     196           0 :       memcpy( config->net.interface, config->development.netns.interface0, sizeof(config->net.interface) );
     197           0 :     }
     198             : 
     199           0 :     if( !strcmp( config->development.pktgen.fake_dst_ip, "" ) ) {
     200           0 :       memcpy( config->development.pktgen.fake_dst_ip, config->development.netns.interface1_addr, sizeof(config->development.netns.interface1_addr) );
     201           0 :     }
     202             : 
     203           0 :     if( FD_UNLIKELY( strcmp( config->development.netns.interface0, config->net.interface ) ) ) {
     204           0 :       FD_LOG_ERR(( "netns interface and firedancer interface are different. If you are using the "
     205           0 :                    "[development.netns] functionality to run Firedancer in a network namespace "
     206           0 :                    "for development, the configuration file must specify that "
     207           0 :                    "[development.netns.interface0] is the same as [net.interface]" ));
     208           0 :     }
     209             : 
     210           0 :     if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->development.netns.interface0_addr, &config->net.ip_addr ) ) )
     211           0 :       FD_LOG_ERR(( "configuration specifies invalid netns IP address `%s`", config->development.netns.interface0_addr ));
     212           6 :   } else { /* !config->development.netns.enabled */
     213           6 :     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           6 :     uint iface_ip;
     216           6 :     if( FD_UNLIKELY( -1==fd_net_util_if_addr( config->net.interface, &iface_ip ) ) )
     217           0 :       FD_LOG_ERR(( "could not get IP address for interface `%s`", config->net.interface ));
     218             : 
     219           6 :     if( FD_UNLIKELY( config->is_firedancer ) ) {
     220           3 :       if( FD_UNLIKELY( strcmp( config->firedancer.gossip.host, "" ) ) ) {
     221           0 :         uint gossip_ip_addr = iface_ip;
     222           0 :         int  has_gossip_ip4 = 0;
     223           0 :         if( FD_UNLIKELY( strlen( config->firedancer.gossip.host )<=15UL ) ) {
     224             :           /* Only sets gossip_ip_addr if it's a valid IPv4 address, otherwise assume it's a DNS name */
     225           0 :           has_gossip_ip4 = fd_cstr_to_ip4_addr( config->firedancer.gossip.host, &gossip_ip_addr );
     226           0 :         }
     227           0 :         if( FD_UNLIKELY( !fd_ip4_addr_is_public( gossip_ip_addr ) && config->is_live_cluster && has_gossip_ip4 ) )
     228           0 :           FD_LOG_ERR(( "Trying to use [gossip.host] " FD_IP4_ADDR_FMT " for listening to incoming "
     229           0 :                       "transactions, but it is part of a private network and will not be routable "
     230           0 :                       "for other Solana network nodes.", FD_IP4_ADDR_FMT_ARGS( iface_ip ) ));
     231           3 :       } else if( FD_UNLIKELY( !fd_ip4_addr_is_public( iface_ip ) && config->is_live_cluster ) ) {
     232           0 :         FD_LOG_ERR(( "Trying to use network interface `%s` for listening to incoming transactions, "
     233           0 :                     "but it has IPv4 address " FD_IP4_ADDR_FMT " which is part of a private network "
     234           0 :                     "and will not be routable for other Solana network nodes. If you are running "
     235           0 :                     "behind a NAT and this interface is publicly reachable, you can continue by "
     236           0 :                     "manually specifying the IP address to advertise in your configuration under "
     237           0 :                     "[gossip.host].", config->net.interface, FD_IP4_ADDR_FMT_ARGS( iface_ip ) ));
     238           0 :       }
     239           3 :     }
     240             : 
     241           6 :     config->net.ip_addr = iface_ip;
     242           6 :   }
     243           6 : }
     244             : 
     245             : void
     246             : fd_config_fill( fd_config_t * config,
     247             :                 int           netns,
     248           6 :                 int           is_local_cluster ) {
     249           6 :   if( FD_UNLIKELY( netns ) ) {
     250           0 :     config->development.netns.enabled = 1;
     251           0 :     strncpy( config->net.interface,
     252           0 :              config->development.netns.interface0,
     253           0 :              sizeof(config->net.interface) );
     254           0 :     config->net.interface[ sizeof(config->net.interface) - 1 ] = '\0';
     255           0 :   }
     256             : 
     257           6 :   struct utsname utsname;
     258           6 :   if( FD_UNLIKELY( -1==uname( &utsname ) ) )
     259           0 :     FD_LOG_ERR(( "could not get uname (%i-%s)", errno, fd_io_strerror( errno ) ));
     260           6 :   strncpy( config->hostname, utsname.nodename, sizeof(config->hostname) );
     261           6 :   config->hostname[ sizeof(config->hostname)-1UL ] = '\0'; /* Just truncate the name if it's too long to fit */
     262             : 
     263           6 :   ulong cluster = FD_CLUSTER_UNKNOWN;
     264           6 :   if( FD_UNLIKELY( !config->is_firedancer ) ) {
     265           3 :     cluster = fd_genesis_cluster_identify( config->frankendancer.consensus.expected_genesis_hash );
     266           3 :   }
     267           6 :   config->is_live_cluster = cluster!=FD_CLUSTER_UNKNOWN;
     268           6 :   strcpy( config->cluster, fd_genesis_cluster_name( cluster ) );
     269             : 
     270           6 :   if( FD_UNLIKELY( !strcmp( config->user, "" ) ) ) {
     271           6 :     const char * user = fd_sys_util_login_user();
     272           6 :     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." ));
     273           6 :     if( FD_UNLIKELY( strlen( user )>=sizeof( config->user ) ) )                                FD_LOG_ERR(( "user name `%s` is too long", user ));
     274           6 :     strncpy( config->user, user, sizeof(config->user) );
     275           6 :   }
     276             : 
     277           6 :   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 ));
     278           6 :   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" ));
     279           6 :   if( FD_UNLIKELY( getuid()!=0U && config->uid!=getuid() ) )                                   FD_LOG_ERR(( "running as uid %u, but config specifies uid %u", getuid(), config->uid ));
     280           6 :   if( FD_UNLIKELY( getgid()!=0U && config->gid!=getgid() ) )                                   FD_LOG_ERR(( "running as gid %u, but config specifies gid %u", getgid(), config->gid ));
     281             : 
     282           6 :   FD_TEST( fd_cstr_printf_check( config->hugetlbfs.gigantic_page_mount_path,
     283           6 :     sizeof(config->hugetlbfs.gigantic_page_mount_path),
     284           6 :     NULL,
     285           6 :     "%s/.gigantic",
     286           6 :     config->hugetlbfs.mount_path ) );
     287           6 :   FD_TEST( fd_cstr_printf_check( config->hugetlbfs.huge_page_mount_path,
     288           6 :     sizeof(config->hugetlbfs.huge_page_mount_path),
     289           6 :     NULL,
     290           6 :     "%s/.huge",
     291           6 :     config->hugetlbfs.mount_path ) );
     292           6 :   FD_TEST( fd_cstr_printf_check( config->hugetlbfs.normal_page_mount_path,
     293           6 :     sizeof(config->hugetlbfs.normal_page_mount_path),
     294           6 :     NULL,
     295           6 :     "%s/.normal",
     296           6 :     config->hugetlbfs.mount_path ) );
     297             : 
     298           6 :   ulong max_page_sz = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size );
     299           6 :   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\"" ));
     300             : 
     301           6 :   replace( config->log.path, "{user}", config->user );
     302           6 :   replace( config->log.path, "{name}", config->name );
     303             : 
     304           6 :   if( FD_LIKELY( !strcmp( "auto", config->log.colorize ) ) )       config->log.colorize1 = 2;
     305           0 :   else if( FD_LIKELY( !strcmp( "true", config->log.colorize ) ) )  config->log.colorize1 = 1;
     306           0 :   else if( FD_LIKELY( !strcmp( "false", config->log.colorize ) ) ) config->log.colorize1 = 0;
     307           0 :   else  FD_LOG_ERR(( "[log.colorize] must be one of \"auto\", \"true\", or \"false\"" ));
     308             : 
     309           6 :   if( FD_LIKELY( 2==config->log.colorize1 ) ) {
     310           6 :     char const * cstr = fd_env_strip_cmdline_cstr( NULL, NULL, NULL, "COLORTERM", NULL );
     311           6 :     int truecolor = cstr && !strcmp( cstr, "truecolor" );
     312             : 
     313           6 :     cstr = fd_env_strip_cmdline_cstr( NULL, NULL, NULL, "TERM", NULL );
     314           6 :     int xterm256color = cstr && !strcmp( cstr, "xterm-256color" );
     315             : 
     316           6 :     config->log.colorize1 = truecolor || xterm256color;
     317           6 :   }
     318             : 
     319           6 :   config->log.level_logfile1 = parse_log_level( config->log.level_logfile );
     320           6 :   config->log.level_stderr1  = parse_log_level( config->log.level_stderr );
     321           6 :   config->log.level_flush1   = parse_log_level( config->log.level_flush );
     322           6 :   if( FD_UNLIKELY( -1==config->log.level_logfile1 ) ) FD_LOG_ERR(( "unrecognized [log.level_logfile] `%s`", config->log.level_logfile ));
     323           6 :   if( FD_UNLIKELY( -1==config->log.level_stderr1 ) )  FD_LOG_ERR(( "unrecognized [log.level_stderr] `%s`", config->log.level_logfile ));
     324           6 :   if( FD_UNLIKELY( -1==config->log.level_flush1 ) )   FD_LOG_ERR(( "unrecognized [log.level_flush] `%s`", config->log.level_logfile ));
     325             : 
     326           6 :   replace( config->paths.base, "{user}", config->user );
     327           6 :   replace( config->paths.base, "{name}", config->name );
     328             : 
     329           6 :   if( FD_UNLIKELY( strcmp( config->paths.ledger, "" ) ) ) {
     330           0 :     replace( config->paths.ledger, "{user}", config->user );
     331           0 :     replace( config->paths.ledger, "{name}", config->name );
     332           6 :   } else {
     333           6 :     FD_TEST( fd_cstr_printf_check( config->paths.ledger, sizeof(config->paths.ledger), NULL, "%s/ledger", config->paths.base ) );
     334           6 :   }
     335             : 
     336           6 :   if( FD_UNLIKELY( !strcmp( config->paths.identity_key, "" ) ) ) {
     337           6 :     if( FD_UNLIKELY( config->is_live_cluster ) ) FD_LOG_ERR(( "configuration file must specify [consensus.identity_path] when joining a live cluster" ));
     338             : 
     339           6 :     FD_TEST( fd_cstr_printf_check( config->paths.identity_key,
     340           6 :                                    sizeof(config->paths.identity_key),
     341           6 :                                    NULL,
     342           6 :                                    "%s/identity.json",
     343           6 :                                    config->paths.base ) );
     344           6 :   } else {
     345           0 :     replace( config->paths.identity_key, "{user}", config->user );
     346           0 :     replace( config->paths.identity_key, "{name}", config->name );
     347           0 :   }
     348             : 
     349           6 :   replace( config->paths.vote_account, "{user}", config->user );
     350           6 :   replace( config->paths.vote_account, "{name}", config->name );
     351             : 
     352           6 :   if( FD_UNLIKELY( strcmp( config->paths.snapshots, "" ) ) ) {
     353           0 :     replace( config->paths.snapshots, "{user}", config->user );
     354           0 :     replace( config->paths.snapshots, "{name}", config->name );
     355           6 :   } else {
     356           6 :     FD_TEST( fd_cstr_printf_check( config->paths.snapshots, sizeof(config->paths.snapshots), NULL, "%s/snapshots", config->paths.base ) );
     357           6 :   }
     358             : 
     359           6 :   if( FD_UNLIKELY( strcmp( config->paths.genesis, "" ) ) ) {
     360           0 :     replace( config->paths.genesis, "{user}", config->user );
     361           0 :     replace( config->paths.genesis, "{name}", config->name );
     362           6 :   } else {
     363           6 :     FD_TEST( fd_cstr_printf_check( config->paths.genesis, sizeof(config->paths.genesis), NULL, "%s/genesis.bin", config->paths.base ) );
     364           6 :   }
     365             : 
     366           6 :   long ts = -fd_log_wallclock();
     367           6 :   config->tick_per_ns_mu = fd_tempo_tick_per_ns( &config->tick_per_ns_sigma );
     368           6 :   FD_LOG_INFO(( "calibrating fd_tempo tick_per_ns took %ld ms", (fd_log_wallclock()+ts)/(1000L*1000L) ));
     369             : 
     370           6 :   if( 0!=strcmp( config->net.bind_address, "" ) ) {
     371           0 :     if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->net.bind_address, &config->net.bind_address_parsed ) ) ) {
     372           0 :       FD_LOG_ERR(( "`net.bind_address` is not a valid IPv4 address" ));
     373           0 :     }
     374           0 :   }
     375             : 
     376           6 :   if(      FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "perf"     ) ) ) config->tiles.pack.schedule_strategy_enum = 0;
     377           0 :   else if( FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "balanced" ) ) ) config->tiles.pack.schedule_strategy_enum = 1;
     378           0 :   else if( FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "revenue"  ) ) ) config->tiles.pack.schedule_strategy_enum = 2;
     379           0 :   else FD_LOG_ERR(( "[tiles.pack.schedule_strategy] %s not recognized", config->tiles.pack.schedule_strategy ));
     380             : 
     381           6 :   fd_config_fill_net( config );
     382             : 
     383           6 :   if( FD_UNLIKELY( config->is_firedancer ) ) {
     384           3 :     fd_config_fillf( config );
     385           3 :   } else {
     386           3 :     fd_config_fillh( config );
     387           3 :   }
     388             : 
     389           6 :   if( FD_LIKELY( config->is_live_cluster) ) {
     390           0 :     if( FD_UNLIKELY( !config->development.sandbox ) )                            FD_LOG_ERR(( "trying to join a live cluster, but configuration disables the sandbox which is a a development only feature" ));
     391           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" ));
     392           0 :     if( FD_UNLIKELY( config->development.netns.enabled ) )                       FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.netns] which is a development only feature" ));
     393           0 :     if( FD_UNLIKELY( config->development.bench.larger_max_cost_per_block ) )     FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.bench.larger_max_cost_per_block] which is a development only feature" ));
     394           0 :     if( FD_UNLIKELY( config->development.bench.larger_shred_limits_per_block ) ) FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.bench.larger_shred_limits_per_block] which is a development only feature" ));
     395           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" ));
     396           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" ));
     397           0 :   }
     398             : 
     399             :   /* When running a local cluster, some options are overriden by default
     400             :      to make starting and running in development environments a little
     401             :      easier and less strict. */
     402           6 :   if( FD_UNLIKELY( is_local_cluster ) ) {
     403           0 :     if( FD_LIKELY( !strcmp( config->paths.vote_account, "" ) ) ) {
     404           0 :       FD_TEST( fd_cstr_printf_check( config->paths.vote_account,
     405           0 :                                      sizeof( config->paths.vote_account ),
     406           0 :                                      NULL,
     407           0 :                                      "%s/vote-account.json",
     408           0 :                                      config->paths.base ) );
     409           0 :     }
     410             : 
     411           0 :     strncpy( config->cluster, "development", sizeof(config->cluster) );
     412             : 
     413           0 :     if( FD_UNLIKELY( !config->is_firedancer ) ) {
     414             :       /* By default only_known is true for validators to ensure secure
     415             :         snapshot download, but in development it doesn't matter and
     416             :         often the developer does not provide known peers. */
     417           0 :       config->frankendancer.rpc.only_known = 0;
     418             : 
     419             :       /* When starting from a new genesis block, this needs to be off else
     420             :         the validator will get stuck forever. */
     421           0 :       config->frankendancer.consensus.wait_for_vote_to_start_leader = 0;
     422             : 
     423             :       /* We have to wait until we get a snapshot before we can join a
     424             :         second validator to this one, so make this smaller than the
     425             :         default.  */
     426           0 :       config->frankendancer.snapshots.full_snapshot_interval_slots = fd_uint_min( config->frankendancer.snapshots.full_snapshot_interval_slots, 200U );
     427           0 :     }
     428           0 :   }
     429             : 
     430           6 :   if( FD_UNLIKELY( config->is_firedancer && config->is_live_cluster && cluster!=FD_CLUSTER_TESTNET ) )
     431           0 :     FD_LOG_ERR(( "Attempted to start against live cluster `%s`. Firedancer is not "
     432           6 :                  "ready for production deployment, has not been tested, and is "
     433           6 :                  "missing consensus critical functionality. Joining a live Solana "
     434           6 :                  "cluster may destabilize the network. Please do not attempt. You "
     435           6 :                  "can start against the testnet cluster by specifying the testnet "
     436           6 :                  "entrypoints from https://docs.solana.com/clusters under "
     437           6 :                  "[gossip.entrypoints] in your configuration file.", fd_genesis_cluster_name( cluster ) ));
     438           6 : }
     439             : 
     440         171 : #define CFG_HAS_NON_EMPTY( key ) do {                  \
     441         171 :   if( !strnlen( config->key, sizeof(config->key) ) ) { \
     442           0 :     FD_LOG_ERR(( "missing `%s`", #key ));              \
     443           0 :   }                                                    \
     444         171 : } while(0)
     445             : 
     446         261 : #define CFG_HAS_NON_ZERO( key ) do {                           \
     447         261 :   if( !config->key ) { FD_LOG_ERR(( "missing `%s`", #key )); } \
     448         261 : } while(0)
     449             : 
     450          21 : #define CFG_HAS_POW2( key ) do {                       \
     451          21 :   ulong value = (ulong)( config -> key );              \
     452          21 :   if( !value || !fd_ulong_is_pow2( value ) ) {         \
     453           0 :     FD_LOG_ERR(( "`%s` must be a power of 2", #key )); \
     454           0 :   }                                                    \
     455          21 : } while(0)
     456             : 
     457             : static void
     458           3 : fd_config_validatef( fd_configf_t const * config ) {
     459           3 :   CFG_HAS_NON_ZERO( layout.sign_tile_count );
     460           3 :   if( FD_UNLIKELY( config->layout.sign_tile_count < 2 ) ) {
     461           0 :     FD_LOG_ERR(( "layout.sign_tile_count must be >= 2" ));
     462           0 :   }
     463           3 : }
     464             : 
     465             : static void
     466           6 : fd_config_validateh( fd_configh_t const * config ) {
     467           6 :   CFG_HAS_NON_EMPTY( dynamic_port_range );
     468             : 
     469           6 :   CFG_HAS_NON_EMPTY( ledger.snapshot_archive_format );
     470             : 
     471           6 :   CFG_HAS_NON_ZERO( snapshots.full_snapshot_interval_slots );
     472           6 :   CFG_HAS_NON_ZERO( snapshots.incremental_snapshot_interval_slots );
     473           6 :   CFG_HAS_NON_ZERO( snapshots.minimum_snapshot_download_speed );
     474           6 :   CFG_HAS_NON_ZERO( snapshots.maximum_snapshot_download_abort );
     475             : 
     476           6 :   CFG_HAS_NON_EMPTY( layout.agave_affinity );
     477           6 : }
     478             : 
     479             : void
     480           9 : fd_config_validate( fd_config_t const * config ) {
     481           9 :   if( FD_LIKELY( config->is_firedancer ) ) {
     482           3 :     fd_config_validatef( &config->firedancer );
     483           6 :   } else {
     484           6 :     fd_config_validateh( &config->frankendancer );
     485           6 :   }
     486             : 
     487           9 :   CFG_HAS_NON_EMPTY( name );
     488           9 :   CFG_HAS_NON_EMPTY( paths.base );
     489             : 
     490           9 :   CFG_HAS_NON_EMPTY( log.colorize );
     491           9 :   CFG_HAS_NON_EMPTY( log.level_logfile );
     492           9 :   CFG_HAS_NON_EMPTY( log.level_stderr );
     493           9 :   CFG_HAS_NON_EMPTY( log.level_flush );
     494             : 
     495           9 :   CFG_HAS_NON_EMPTY( layout.affinity );
     496           9 :   CFG_HAS_NON_ZERO ( layout.net_tile_count );
     497           9 :   CFG_HAS_NON_ZERO ( layout.quic_tile_count );
     498           9 :   CFG_HAS_NON_ZERO ( layout.resolv_tile_count );
     499           9 :   CFG_HAS_NON_ZERO ( layout.verify_tile_count );
     500           9 :   CFG_HAS_NON_ZERO ( layout.bank_tile_count  );
     501           9 :   CFG_HAS_NON_ZERO ( layout.shred_tile_count );
     502             : 
     503           9 :   if( 0U!=config->firedancer.layout.writer_tile_count ) {
     504           3 :     if( FD_UNLIKELY( config->firedancer.layout.writer_tile_count>config->firedancer.layout.exec_tile_count ) ) {
     505             :       /* There can be at most 1 in-flight transaction per exec tile
     506             :          awaiting finalization. */
     507           0 :       FD_LOG_ERR(( "More writer tiles (%u) than exec tiles (%u)", config->firedancer.layout.writer_tile_count, config->firedancer.layout.exec_tile_count ));
     508           0 :     }
     509           3 :   }
     510             : 
     511           9 :   CFG_HAS_NON_EMPTY( hugetlbfs.mount_path );
     512           9 :   CFG_HAS_NON_EMPTY( hugetlbfs.max_page_size );
     513             : 
     514           9 :   CFG_HAS_NON_ZERO( net.ingress_buffer_size );
     515           9 :   if( 0==strcmp( config->net.provider, "xdp" ) ) {
     516           9 :     CFG_HAS_NON_EMPTY( net.xdp.xdp_mode );
     517           9 :     CFG_HAS_POW2     ( net.xdp.xdp_rx_queue_size );
     518           9 :     CFG_HAS_POW2     ( net.xdp.xdp_tx_queue_size );
     519           9 :     if( 0==strcmp( config->net.xdp.rss_queue_mode, "dedicated" ) ) {
     520           0 :       if( FD_UNLIKELY( config->layout.net_tile_count != 1 ) )
     521           0 :         FD_LOG_ERR(( "`layout.net_tile_count` must be 1 when `net.xdp.rss_queue_mode` is \"dedicated\"" ));
     522           9 :     } else if( 0!=strcmp( config->net.xdp.rss_queue_mode, "simple" ) ) {
     523           0 :       FD_LOG_ERR(( "invalid `net.xdp.rss_queue_mode`: \"%s\"; must be \"simple\" or \"dedicated\"",
     524           0 :                    config->net.xdp.rss_queue_mode  ));
     525           0 :     }
     526           9 :   } else if( 0==strcmp( config->net.provider, "socket" ) ) {
     527           0 :     CFG_HAS_NON_ZERO( net.socket.receive_buffer_size );
     528           0 :     CFG_HAS_NON_ZERO( net.socket.send_buffer_size );
     529           0 :   } else {
     530           0 :     FD_LOG_ERR(( "invalid `net.provider`: must be \"xdp\" or \"socket\"" ));
     531           0 :   }
     532             : 
     533           9 :   CFG_HAS_NON_ZERO( tiles.netlink.max_routes           );
     534           9 :   CFG_HAS_NON_ZERO( tiles.netlink.max_peer_routes      );
     535           9 :   CFG_HAS_NON_ZERO( tiles.netlink.max_neighbors        );
     536             : 
     537           9 :   CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_connections );
     538           9 :   CFG_HAS_NON_ZERO( tiles.quic.txn_reassembly_count );
     539           9 :   CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_handshakes );
     540           9 :   CFG_HAS_NON_ZERO( tiles.quic.idle_timeout_millis );
     541             : 
     542           9 :   CFG_HAS_NON_ZERO( tiles.verify.signature_cache_size );
     543           9 :   CFG_HAS_NON_ZERO( tiles.verify.receive_buffer_size );
     544             : 
     545           9 :   CFG_HAS_NON_ZERO( tiles.dedup.signature_cache_size );
     546             : 
     547           9 :   CFG_HAS_NON_ZERO( tiles.pack.max_pending_transactions );
     548             : 
     549           9 :   CFG_HAS_NON_ZERO( tiles.shred.max_pending_shred_sets );
     550             : 
     551           9 :   if( config->is_firedancer ) {
     552           3 :     CFG_HAS_POW2( tiles.repair.slot_max );
     553           3 :   }
     554             : 
     555           9 :   if( FD_UNLIKELY( config->tiles.bundle.keepalive_interval_millis <    3000 &&
     556           9 :                    config->tiles.bundle.keepalive_interval_millis > 3600000 ) ) {
     557           0 :     FD_LOG_ERR(( "`tiles.bundle.keepalive_interval_millis` must be in range [3000, 3,600,000]" ));
     558           0 :   }
     559             : 
     560           9 :   CFG_HAS_NON_EMPTY( development.netns.interface0 );
     561           9 :   CFG_HAS_NON_EMPTY( development.netns.interface0_mac );
     562           9 :   CFG_HAS_NON_EMPTY( development.netns.interface0_addr );
     563           9 :   CFG_HAS_NON_EMPTY( development.netns.interface1 );
     564           9 :   CFG_HAS_NON_EMPTY( development.netns.interface1_mac );
     565           9 :   CFG_HAS_NON_EMPTY( development.netns.interface1_addr );
     566             : 
     567           9 :   CFG_HAS_NON_ZERO( development.genesis.target_tick_duration_micros );
     568           9 :   CFG_HAS_NON_ZERO( development.genesis.ticks_per_slot );
     569           9 :   CFG_HAS_NON_ZERO( development.genesis.fund_initial_accounts );
     570           9 :   CFG_HAS_NON_ZERO( development.genesis.fund_initial_amount_lamports );
     571             : 
     572           9 :   CFG_HAS_NON_ZERO ( development.bench.benchg_tile_count );
     573           9 :   CFG_HAS_NON_ZERO ( development.bench.benchs_tile_count );
     574           9 :   CFG_HAS_NON_EMPTY( development.bench.affinity );
     575             : 
     576           9 :   CFG_HAS_NON_ZERO( development.bundle.ssl_heap_size_mib );
     577           9 : }
     578             : 
     579             : #undef CFG_HAS_NON_EMPTY
     580             : #undef CFG_HAS_NON_ZERO
     581             : #undef CFG_HAS_POW2
     582             : 
     583             : void
     584             : fd_config_load( int           is_firedancer,
     585             :                 int           netns,
     586             :                 int           is_local_cluster,
     587             :                 char const *  default_config,
     588             :                 ulong         default_config_sz,
     589             :                 char const *  override_config,
     590             :                 char const *  override_config_path,
     591             :                 ulong         override_config_sz,
     592             :                 char const *  user_config,
     593             :                 ulong         user_config_sz,
     594             :                 char const *  user_config_path,
     595           6 :                 fd_config_t * config ) {
     596           6 :   memset( config, 0, sizeof(config_t) );
     597           6 :   config->is_firedancer = is_firedancer;
     598           6 :   config->boot_timestamp_nanos = fd_log_wallclock();
     599             : 
     600           6 :   fd_config_load_buf( config, default_config, default_config_sz, "default.toml" );
     601           6 :   fd_config_validate( config );
     602           6 :   if( FD_UNLIKELY( override_config ) ) {
     603           0 :     fd_config_load_buf( config, override_config, override_config_sz, override_config_path );
     604           0 :     fd_config_validate( config );
     605           0 :   }
     606           6 :   if( FD_LIKELY( user_config ) ) {
     607           0 :     fd_config_load_buf( config, user_config, user_config_sz, user_config_path );
     608           0 :     fd_config_validate( config );
     609           0 :   }
     610             : 
     611           6 :   fd_config_fill( config, netns, is_local_cluster);
     612           6 : }
     613             : 
     614             : int
     615           0 : fd_config_to_memfd( fd_config_t const * config ) {
     616           0 :   int config_memfd = memfd_create( "fd_config", 0 );
     617           0 :   if( FD_UNLIKELY( -1==config_memfd ) ) return -1;
     618           0 :   if( FD_UNLIKELY( -1==ftruncate( config_memfd, sizeof( config_t ) ) ) ) {
     619           0 :     if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_WARNING(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     620           0 :     return -1;
     621           0 :   }
     622             : 
     623           0 :   uchar * bytes = mmap( NULL, sizeof( config_t ), PROT_READ|PROT_WRITE, MAP_SHARED, config_memfd, 0 );
     624           0 :   if( FD_UNLIKELY( bytes==MAP_FAILED ) ) {
     625           0 :     if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_WARNING(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     626           0 :     return -1;
     627           0 :   }
     628             : 
     629           0 :   fd_memcpy( bytes, config, sizeof( config_t ) );
     630           0 :   if( FD_UNLIKELY( munmap( bytes, sizeof( config_t ) ) ) ) FD_LOG_ERR(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     631             : 
     632           0 :   return config_memfd;
     633           0 : }

Generated by: LCOV version 1.14