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 color256 = cstr && strstr( cstr, "256color" );
315 :
316 6 : config->log.colorize1 = truecolor || color256;
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 :
390 6 : if( FD_LIKELY( !strcmp( config->development.gui.frontend_release_channel, "stable" ) ) ) config->development.gui.frontend_release_channel_enum = 0;
391 3 : else if( FD_LIKELY( !strcmp( config->development.gui.frontend_release_channel, "alpha" ) ) ) config->development.gui.frontend_release_channel_enum = 1;
392 0 : else FD_LOG_ERR(( "[development.gui.release_channel] %s not recognized", config->development.gui.frontend_release_channel ));
393 :
394 6 : if( FD_LIKELY( config->is_live_cluster) ) {
395 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" ));
396 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" ));
397 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" ));
398 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" ));
399 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" ));
400 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" ));
401 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" ));
402 0 : }
403 :
404 : /* When running a local cluster, some options are overriden by default
405 : to make starting and running in development environments a little
406 : easier and less strict. */
407 6 : if( FD_UNLIKELY( is_local_cluster ) ) {
408 0 : if( FD_LIKELY( !strcmp( config->paths.vote_account, "" ) ) ) {
409 0 : FD_TEST( fd_cstr_printf_check( config->paths.vote_account,
410 0 : sizeof( config->paths.vote_account ),
411 0 : NULL,
412 0 : "%s/vote-account.json",
413 0 : config->paths.base ) );
414 0 : }
415 :
416 0 : strncpy( config->cluster, "development", sizeof(config->cluster) );
417 :
418 0 : if( FD_UNLIKELY( !config->is_firedancer ) ) {
419 : /* By default only_known is true for validators to ensure secure
420 : snapshot download, but in development it doesn't matter and
421 : often the developer does not provide known peers. */
422 0 : config->frankendancer.rpc.only_known = 0;
423 :
424 : /* When starting from a new genesis block, this needs to be off else
425 : the validator will get stuck forever. */
426 0 : config->frankendancer.consensus.wait_for_vote_to_start_leader = 0;
427 :
428 : /* We have to wait until we get a snapshot before we can join a
429 : second validator to this one, so make this smaller than the
430 : default. */
431 0 : config->frankendancer.snapshots.full_snapshot_interval_slots = fd_uint_min( config->frankendancer.snapshots.full_snapshot_interval_slots, 200U );
432 0 : }
433 0 : }
434 :
435 6 : if( FD_UNLIKELY( config->is_firedancer && config->is_live_cluster && cluster!=FD_CLUSTER_TESTNET ) )
436 0 : FD_LOG_ERR(( "Attempted to start against live cluster `%s`. Firedancer is not "
437 6 : "ready for production deployment, has not been tested, and is "
438 6 : "missing consensus critical functionality. Joining a live Solana "
439 6 : "cluster may destabilize the network. Please do not attempt. You "
440 6 : "can start against the testnet cluster by specifying the testnet "
441 6 : "entrypoints from https://docs.solana.com/clusters under "
442 6 : "[gossip.entrypoints] in your configuration file.", fd_genesis_cluster_name( cluster ) ));
443 6 : }
444 :
445 171 : #define CFG_HAS_NON_EMPTY( key ) do { \
446 171 : if( !strnlen( config->key, sizeof(config->key) ) ) { \
447 0 : FD_LOG_ERR(( "missing `%s`", #key )); \
448 0 : } \
449 171 : } while(0)
450 :
451 261 : #define CFG_HAS_NON_ZERO( key ) do { \
452 261 : if( !config->key ) { FD_LOG_ERR(( "missing `%s`", #key )); } \
453 261 : } while(0)
454 :
455 21 : #define CFG_HAS_POW2( key ) do { \
456 21 : ulong value = (ulong)( config -> key ); \
457 21 : if( !value || !fd_ulong_is_pow2( value ) ) { \
458 0 : FD_LOG_ERR(( "`%s` must be a power of 2", #key )); \
459 0 : } \
460 21 : } while(0)
461 :
462 : static void
463 3 : fd_config_validatef( fd_configf_t const * config ) {
464 3 : CFG_HAS_NON_ZERO( layout.sign_tile_count );
465 3 : if( FD_UNLIKELY( config->layout.sign_tile_count < 2 ) ) {
466 0 : FD_LOG_ERR(( "layout.sign_tile_count must be >= 2" ));
467 0 : }
468 3 : }
469 :
470 : static void
471 6 : fd_config_validateh( fd_configh_t const * config ) {
472 6 : CFG_HAS_NON_EMPTY( dynamic_port_range );
473 :
474 6 : CFG_HAS_NON_EMPTY( ledger.snapshot_archive_format );
475 :
476 6 : CFG_HAS_NON_ZERO( snapshots.full_snapshot_interval_slots );
477 6 : CFG_HAS_NON_ZERO( snapshots.incremental_snapshot_interval_slots );
478 6 : CFG_HAS_NON_ZERO( snapshots.minimum_snapshot_download_speed );
479 6 : CFG_HAS_NON_ZERO( snapshots.maximum_snapshot_download_abort );
480 :
481 6 : CFG_HAS_NON_EMPTY( layout.agave_affinity );
482 6 : }
483 :
484 : void
485 9 : fd_config_validate( fd_config_t const * config ) {
486 9 : if( FD_LIKELY( config->is_firedancer ) ) {
487 3 : fd_config_validatef( &config->firedancer );
488 6 : } else {
489 6 : fd_config_validateh( &config->frankendancer );
490 6 : }
491 :
492 9 : CFG_HAS_NON_EMPTY( name );
493 9 : CFG_HAS_NON_EMPTY( paths.base );
494 :
495 9 : CFG_HAS_NON_EMPTY( log.colorize );
496 9 : CFG_HAS_NON_EMPTY( log.level_logfile );
497 9 : CFG_HAS_NON_EMPTY( log.level_stderr );
498 9 : CFG_HAS_NON_EMPTY( log.level_flush );
499 :
500 9 : CFG_HAS_NON_EMPTY( layout.affinity );
501 9 : CFG_HAS_NON_ZERO ( layout.net_tile_count );
502 9 : CFG_HAS_NON_ZERO ( layout.quic_tile_count );
503 9 : CFG_HAS_NON_ZERO ( layout.resolv_tile_count );
504 9 : CFG_HAS_NON_ZERO ( layout.verify_tile_count );
505 9 : CFG_HAS_NON_ZERO ( layout.bank_tile_count );
506 9 : CFG_HAS_NON_ZERO ( layout.shred_tile_count );
507 :
508 9 : CFG_HAS_NON_EMPTY( hugetlbfs.mount_path );
509 9 : CFG_HAS_NON_EMPTY( hugetlbfs.max_page_size );
510 :
511 9 : CFG_HAS_NON_ZERO( net.ingress_buffer_size );
512 9 : if( 0==strcmp( config->net.provider, "xdp" ) ) {
513 9 : CFG_HAS_NON_EMPTY( net.xdp.xdp_mode );
514 9 : CFG_HAS_POW2 ( net.xdp.xdp_rx_queue_size );
515 9 : CFG_HAS_POW2 ( net.xdp.xdp_tx_queue_size );
516 9 : if( 0==strcmp( config->net.xdp.rss_queue_mode, "dedicated" ) ) {
517 0 : if( FD_UNLIKELY( config->layout.net_tile_count != 1 ) )
518 0 : FD_LOG_ERR(( "`layout.net_tile_count` must be 1 when `net.xdp.rss_queue_mode` is \"dedicated\"" ));
519 9 : } else if( 0!=strcmp( config->net.xdp.rss_queue_mode, "simple" ) &&
520 9 : 0!=strcmp( config->net.xdp.rss_queue_mode, "auto" ) ) {
521 0 : FD_LOG_ERR(( "invalid `net.xdp.rss_queue_mode`: \"%s\"; must be \"simple\", \"dedicated\", or \"auto\"",
522 0 : config->net.xdp.rss_queue_mode ));
523 0 : }
524 9 : } else if( 0==strcmp( config->net.provider, "socket" ) ) {
525 0 : CFG_HAS_NON_ZERO( net.socket.receive_buffer_size );
526 0 : CFG_HAS_NON_ZERO( net.socket.send_buffer_size );
527 0 : } else {
528 0 : FD_LOG_ERR(( "invalid `net.provider`: must be \"xdp\" or \"socket\"" ));
529 0 : }
530 :
531 9 : CFG_HAS_NON_ZERO( tiles.netlink.max_routes );
532 9 : CFG_HAS_NON_ZERO( tiles.netlink.max_peer_routes );
533 9 : CFG_HAS_NON_ZERO( tiles.netlink.max_neighbors );
534 :
535 9 : CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_connections );
536 9 : CFG_HAS_NON_ZERO( tiles.quic.txn_reassembly_count );
537 9 : CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_handshakes );
538 9 : CFG_HAS_NON_ZERO( tiles.quic.idle_timeout_millis );
539 :
540 9 : CFG_HAS_NON_ZERO( tiles.verify.signature_cache_size );
541 9 : CFG_HAS_NON_ZERO( tiles.verify.receive_buffer_size );
542 :
543 9 : CFG_HAS_NON_ZERO( tiles.dedup.signature_cache_size );
544 :
545 9 : CFG_HAS_NON_ZERO( tiles.pack.max_pending_transactions );
546 :
547 9 : CFG_HAS_NON_ZERO( tiles.shred.max_pending_shred_sets );
548 :
549 9 : if( config->is_firedancer ) {
550 3 : CFG_HAS_POW2( tiles.repair.slot_max );
551 3 : }
552 :
553 9 : if( FD_UNLIKELY( config->tiles.bundle.keepalive_interval_millis < 3000 &&
554 9 : config->tiles.bundle.keepalive_interval_millis > 3600000 ) ) {
555 0 : FD_LOG_ERR(( "`tiles.bundle.keepalive_interval_millis` must be in range [3000, 3,600,000]" ));
556 0 : }
557 :
558 9 : CFG_HAS_NON_EMPTY( development.netns.interface0 );
559 9 : CFG_HAS_NON_EMPTY( development.netns.interface0_mac );
560 9 : CFG_HAS_NON_EMPTY( development.netns.interface0_addr );
561 9 : CFG_HAS_NON_EMPTY( development.netns.interface1 );
562 9 : CFG_HAS_NON_EMPTY( development.netns.interface1_mac );
563 9 : CFG_HAS_NON_EMPTY( development.netns.interface1_addr );
564 :
565 9 : CFG_HAS_NON_ZERO( development.genesis.target_tick_duration_micros );
566 9 : CFG_HAS_NON_ZERO( development.genesis.ticks_per_slot );
567 9 : CFG_HAS_NON_ZERO( development.genesis.fund_initial_accounts );
568 9 : CFG_HAS_NON_ZERO( development.genesis.fund_initial_amount_lamports );
569 :
570 9 : CFG_HAS_NON_ZERO ( development.bench.benchg_tile_count );
571 9 : CFG_HAS_NON_ZERO ( development.bench.benchs_tile_count );
572 9 : CFG_HAS_NON_EMPTY( development.bench.affinity );
573 :
574 9 : CFG_HAS_NON_ZERO( development.bundle.ssl_heap_size_mib );
575 9 : }
576 :
577 : #undef CFG_HAS_NON_EMPTY
578 : #undef CFG_HAS_NON_ZERO
579 : #undef CFG_HAS_POW2
580 :
581 : void
582 : fd_config_load( int is_firedancer,
583 : int netns,
584 : int is_local_cluster,
585 : char const * default_config,
586 : ulong default_config_sz,
587 : char const * override_config,
588 : char const * override_config_path,
589 : ulong override_config_sz,
590 : char const * user_config,
591 : ulong user_config_sz,
592 : char const * user_config_path,
593 6 : fd_config_t * config ) {
594 6 : memset( config, 0, sizeof(config_t) );
595 6 : config->is_firedancer = is_firedancer;
596 6 : config->boot_timestamp_nanos = fd_log_wallclock();
597 :
598 6 : if( FD_UNLIKELY( is_firedancer ) ) {
599 3 : fd_cstr_printf_check( config->development.gui.frontend_release_channel, sizeof(config->development.gui.frontend_release_channel), NULL, "alpha" );
600 3 : } else {
601 3 : fd_cstr_printf_check( config->development.gui.frontend_release_channel, sizeof(config->development.gui.frontend_release_channel), NULL, "stable" );
602 3 : }
603 :
604 6 : fd_config_load_buf( config, default_config, default_config_sz, "default.toml" );
605 6 : fd_config_validate( config );
606 6 : if( FD_UNLIKELY( override_config ) ) {
607 0 : fd_config_load_buf( config, override_config, override_config_sz, override_config_path );
608 0 : fd_config_validate( config );
609 0 : }
610 6 : if( FD_LIKELY( user_config ) ) {
611 0 : fd_config_load_buf( config, user_config, user_config_sz, user_config_path );
612 0 : fd_config_validate( config );
613 0 : }
614 :
615 6 : fd_config_fill( config, netns, is_local_cluster);
616 6 : }
617 :
618 : int
619 0 : fd_config_to_memfd( fd_config_t const * config ) {
620 0 : int config_memfd = memfd_create( "fd_config", 0 );
621 0 : if( FD_UNLIKELY( -1==config_memfd ) ) return -1;
622 0 : if( FD_UNLIKELY( -1==ftruncate( config_memfd, sizeof( config_t ) ) ) ) {
623 0 : if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_WARNING(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
624 0 : return -1;
625 0 : }
626 :
627 0 : uchar * bytes = mmap( NULL, sizeof( config_t ), PROT_READ|PROT_WRITE, MAP_SHARED, config_memfd, 0 );
628 0 : if( FD_UNLIKELY( bytes==MAP_FAILED ) ) {
629 0 : if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_WARNING(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
630 0 : return -1;
631 0 : }
632 :
633 0 : fd_memcpy( bytes, config, sizeof( config_t ) );
634 0 : if( FD_UNLIKELY( munmap( bytes, sizeof( config_t ) ) ) ) FD_LOG_ERR(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
635 :
636 0 : return config_memfd;
637 0 : }
|