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