Line data Source code
1 : #define _GNU_SOURCE
2 : #include "config.h"
3 : #include "config_parse.h"
4 : #include "fdctl.h"
5 :
6 : #include "run/topos/topos.h"
7 :
8 : #include "../../ballet/toml/fd_toml.h"
9 : #include "../../disco/topo/fd_pod_format.h"
10 : #include "../../flamenco/runtime/fd_blockstore.h"
11 : #include "../../flamenco/runtime/fd_txncache.h"
12 : #include "../../funk/fd_funk.h"
13 : #include "../../util/net/fd_eth.h"
14 : #include "../../util/net/fd_ip4.h"
15 :
16 : #include <fcntl.h>
17 : #include <pwd.h>
18 : #include <stdio.h>
19 : #include <stdlib.h>
20 : #include <unistd.h>
21 : #include <net/if.h>
22 : #include <linux/if.h>
23 : #include <arpa/inet.h>
24 : #include <sys/mman.h>
25 : #include <sys/stat.h>
26 : #include <sys/sysinfo.h>
27 : #include <sys/ioctl.h>
28 : #include <sys/utsname.h>
29 : #include <sys/wait.h>
30 :
31 : void
32 : replace( char * in,
33 : const char * pat,
34 18 : const char * sub ) {
35 18 : char * replace = strstr( in, pat );
36 18 : if( FD_LIKELY( replace ) ) {
37 6 : ulong pat_len = strlen( pat );
38 6 : ulong sub_len = strlen( sub );
39 6 : ulong in_len = strlen( in );
40 6 : if( FD_UNLIKELY( pat_len > in_len ) ) return;
41 :
42 6 : ulong total_len = in_len - pat_len + sub_len;
43 6 : if( FD_UNLIKELY( total_len >= PATH_MAX ) )
44 0 : FD_LOG_ERR(( "configuration scratch directory path too long: `%s`", in ));
45 :
46 6 : uchar after[PATH_MAX] = {0};
47 6 : fd_memcpy( after, replace + pat_len, strlen( replace + pat_len ) );
48 6 : fd_memcpy( replace, sub, sub_len );
49 6 : ulong after_len = strlen( ( const char * ) after );
50 6 : fd_memcpy( replace + sub_len, after, after_len );
51 6 : in[ total_len ] = '\0';
52 6 : }
53 18 : }
54 :
55 : static void
56 : fdctl_cfg_load_buf( config_t * out,
57 : char const * buf,
58 : ulong sz,
59 3 : char const * path ) {
60 :
61 3 : static uchar pod_mem[ 1UL<<30 ];
62 3 : uchar * pod = fd_pod_join( fd_pod_new( pod_mem, sizeof(pod_mem) ) );
63 :
64 3 : uchar scratch[ 4096 ];
65 3 : long toml_err = fd_toml_parse( buf, sz, pod, scratch, sizeof(scratch) );
66 3 : if( FD_UNLIKELY( toml_err!=FD_TOML_SUCCESS ) ) {
67 0 : FD_LOG_ERR(( "Invalid config (%s)", path ));
68 0 : }
69 :
70 3 : if( FD_UNLIKELY( !fdctl_pod_to_cfg( out, pod ) ) ) {
71 0 : FD_LOG_ERR(( "Invalid config (%s)", path ));
72 0 : }
73 :
74 3 : fd_pod_delete( fd_pod_leave( pod ) );
75 3 : }
76 :
77 : static void
78 : fdctl_cfg_load_file( config_t * out,
79 0 : char const * path ) {
80 :
81 0 : int fd = open( path, O_RDONLY );
82 0 : if( FD_UNLIKELY( fd<0 ) ) {
83 0 : FD_LOG_ERR(( "open(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
84 0 : }
85 :
86 0 : struct stat st;
87 0 : if( FD_UNLIKELY( fstat( fd, &st ) ) ) {
88 0 : FD_LOG_ERR(( "fstat(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
89 0 : }
90 0 : ulong toml_sz = (ulong)st.st_size;
91 :
92 0 : void * mem = mmap( NULL, toml_sz, PROT_READ, MAP_PRIVATE, fd, 0 );
93 0 : if( FD_UNLIKELY( mem==MAP_FAILED ) ) {
94 0 : FD_LOG_ERR(( "mmap(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
95 0 : }
96 :
97 0 : if( FD_UNLIKELY( 0!=close( fd ) ) ) {
98 0 : FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
99 0 : }
100 :
101 0 : fdctl_cfg_load_buf( out, mem, toml_sz, path );
102 :
103 0 : if( FD_UNLIKELY( 0!=munmap( mem, toml_sz ) ) ) {
104 0 : FD_LOG_ERR(( "munmap(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
105 0 : }
106 0 : }
107 :
108 : static uint
109 3 : listen_address( const char * interface ) {
110 3 : int fd = socket( AF_INET, SOCK_DGRAM, 0 );
111 3 : struct ifreq ifr = {0};
112 3 : ifr.ifr_addr.sa_family = AF_INET;
113 3 : strncpy( ifr.ifr_name, interface, IFNAMSIZ );
114 3 : ifr.ifr_name[ IFNAMSIZ-1 ] = '\0';
115 3 : if( FD_UNLIKELY( ioctl( fd, SIOCGIFADDR, &ifr ) ) )
116 0 : FD_LOG_ERR(( "could not get IP address of interface `%s` (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
117 3 : if( FD_UNLIKELY( close(fd) ) )
118 0 : FD_LOG_ERR(( "could not close socket (%i-%s)", errno, fd_io_strerror( errno ) ));
119 3 : return ((struct sockaddr_in *)fd_type_pun( &ifr.ifr_addr ))->sin_addr.s_addr;
120 3 : }
121 :
122 : static void
123 : mac_address( const char * interface,
124 3 : uchar * mac ) {
125 3 : int fd = socket( AF_INET, SOCK_DGRAM, IPPROTO_IP );
126 3 : struct ifreq ifr;
127 3 : ifr.ifr_addr.sa_family = AF_INET;
128 3 : strncpy( ifr.ifr_name, interface, IFNAMSIZ );
129 3 : if( FD_UNLIKELY( ioctl( fd, SIOCGIFHWADDR, &ifr ) ) )
130 0 : FD_LOG_ERR(( "could not get MAC address of interface `%s`: (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
131 3 : if( FD_UNLIKELY( close(fd) ) )
132 0 : FD_LOG_ERR(( "could not close socket (%i-%s)", errno, fd_io_strerror( errno ) ));
133 3 : fd_memcpy( mac, ifr.ifr_hwaddr.sa_data, 6 );
134 3 : }
135 :
136 : static void
137 3 : username_to_id( config_t * config ) {
138 3 : uint * results = mmap( NULL, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0 );
139 3 : if( FD_UNLIKELY( results==MAP_FAILED ) ) FD_LOG_ERR(( "mmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
140 :
141 3 : results[ 0 ] = UINT_MAX;
142 3 : results[ 1 ] = UINT_MAX;
143 :
144 : /* This is extremely unfortunate. We just want to call getpwnam but
145 : on various glibc it can open `/var/lib/sss/mc/passwd` and then not
146 : close it. We could go and find this file descriptor and close it
147 : for the library, but that is a bit of a hack. Instead we fork a
148 : new process to call getpwnam and then exit.
149 :
150 : We could try just reading /etc/passwd here instead, but the glibc
151 : getpwnam implementation does a lot of things we need, including
152 : potentially reading from NCSD or SSSD. */
153 :
154 3 : pid_t pid = fork();
155 3 : if( FD_UNLIKELY( pid == -1 ) ) FD_LOG_ERR(( "fork() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
156 :
157 3 : if( FD_LIKELY( !pid ) ) {
158 0 : char buf[ 16384 ];
159 0 : struct passwd pwd;
160 0 : struct passwd * result;
161 0 : int error = getpwnam_r( config->user, &pwd, buf, sizeof(buf), &result );
162 0 : if( FD_UNLIKELY( error ) ) {
163 0 : if( FD_LIKELY( error==ENOENT || error==ESRCH ) ) FD_LOG_ERR(( "configuration file wants firedancer to run as user `%s` but it does not exist", config->user ));
164 0 : else FD_LOG_ERR(( "could not get user id for `%s` (%i-%s)", config->user, errno, fd_io_strerror( errno ) ));
165 0 : }
166 0 : if( FD_UNLIKELY( !result ) ) FD_LOG_ERR(( "configuration file wants firedancer to run as user `%s` but it does not exist", config->user ));
167 0 : results[ 0 ] = pwd.pw_uid;
168 0 : results[ 1 ] = pwd.pw_gid;
169 0 : exit_group( 0 );
170 3 : } else {
171 3 : int wstatus;
172 3 : if( FD_UNLIKELY( waitpid( pid, &wstatus, 0 )==-1 ) ) FD_LOG_ERR(( "waitpid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
173 3 : if( FD_UNLIKELY( WIFSIGNALED( wstatus ) ) )
174 0 : FD_LOG_ERR(( "uid fetch process terminated by signal %i-%s", WTERMSIG( wstatus ), fd_io_strsignal( WTERMSIG( wstatus ) ) ));
175 3 : if( FD_UNLIKELY( WEXITSTATUS( wstatus ) ) )
176 0 : FD_LOG_ERR(( "uid fetch process exited with status %i", WEXITSTATUS( wstatus ) ));
177 3 : }
178 :
179 3 : if( FD_UNLIKELY( results[ 0 ]==UINT_MAX || results[ 1 ]==UINT_MAX ) ) FD_LOG_ERR(( "could not get user id for `%s`", config->user ));
180 3 : config->uid = results[ 0 ];
181 3 : config->gid = results[ 1 ];
182 :
183 3 : if( FD_UNLIKELY( munmap( results, 4096 ) ) ) FD_LOG_ERR(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
184 3 : }
185 :
186 : FD_FN_CONST fd_topo_run_tile_t *
187 207 : fd_topo_tile_to_config( fd_topo_tile_t const * tile ) {
188 207 : fd_topo_run_tile_t ** run = TILES;
189 1404 : while( *run ) {
190 1404 : if( FD_LIKELY( !strcmp( (*run)->name, tile->name ) ) ) return *run;
191 1197 : run++;
192 1197 : }
193 0 : FD_LOG_ERR(( "unknown tile name `%s`", tile->name ));
194 0 : }
195 :
196 : ulong
197 : fdctl_obj_align( fd_topo_t const * topo,
198 495 : fd_topo_obj_t const * obj ) {
199 495 : if( FD_UNLIKELY( !strcmp( obj->name, "tile" ) ) ) {
200 69 : fd_topo_tile_t const * tile = NULL;
201 828 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
202 828 : if( FD_LIKELY( topo->tiles[ i ].tile_obj_id==obj->id ) ) {
203 69 : tile = &topo->tiles[ i ];
204 69 : break;
205 69 : }
206 828 : }
207 69 : fd_topo_run_tile_t * config = fd_topo_tile_to_config( tile );
208 69 : if( FD_LIKELY( config->scratch_align ) ) return config->scratch_align();
209 0 : return 1UL;
210 426 : } else if( FD_UNLIKELY( !strcmp( obj->name, "mcache" ) ) ) {
211 99 : return fd_mcache_align();
212 327 : } else if( FD_UNLIKELY( !strcmp( obj->name, "dcache" ) ) ) {
213 99 : return fd_dcache_align();
214 228 : } else if( FD_UNLIKELY( !strcmp( obj->name, "cnc" ) ) ) {
215 0 : return fd_cnc_align();
216 228 : } else if( FD_UNLIKELY( !strcmp( obj->name, "fseq" ) ) ) {
217 159 : return fd_fseq_align();
218 159 : } else if( FD_UNLIKELY( !strcmp( obj->name, "metrics" ) ) ) {
219 69 : return FD_METRICS_ALIGN;
220 69 : } else if( FD_UNLIKELY( !strcmp( obj->name, "blockstore" ) ) ) {
221 0 : return fd_blockstore_align();
222 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "funk" ) ) ) {
223 0 : return fd_funk_align();
224 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "txncache" ) ) ) {
225 0 : return fd_txncache_align();
226 0 : } else {
227 0 : FD_LOG_ERR(( "unknown object `%s`", obj->name ));
228 0 : return 0UL;
229 0 : }
230 495 : }
231 :
232 : ulong
233 : fdctl_obj_footprint( fd_topo_t const * topo,
234 495 : fd_topo_obj_t const * obj ) {
235 495 : #define VAL(name) (__extension__({ \
236 396 : ulong __x = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.%s", obj->id, name ); \
237 396 : if( FD_UNLIKELY( __x==ULONG_MAX ) ) FD_LOG_ERR(( "obj.%lu.%s was not set", obj->id, name )); \
238 396 : __x; }))
239 :
240 495 : if( FD_UNLIKELY( !strcmp( obj->name, "tile" ) ) ) {
241 69 : fd_topo_tile_t const * tile = NULL;
242 828 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
243 828 : if( FD_LIKELY( topo->tiles[ i ].tile_obj_id==obj->id ) ) {
244 69 : tile = &topo->tiles[ i ];
245 69 : break;
246 69 : }
247 828 : }
248 69 : fd_topo_run_tile_t * config = fd_topo_tile_to_config( tile );
249 69 : if( FD_LIKELY( config->scratch_footprint ) ) return config->scratch_footprint( tile );
250 0 : return 0UL;
251 426 : } else if( FD_UNLIKELY( !strcmp( obj->name, "mcache" ) ) ) {
252 99 : return fd_mcache_footprint( VAL("depth"), 0UL );
253 327 : } else if( FD_UNLIKELY( !strcmp( obj->name, "dcache" ) ) ) {
254 99 : return fd_dcache_footprint( fd_dcache_req_data_sz( VAL("mtu"), VAL("depth"), VAL("burst"), 1), 0UL );
255 228 : } else if( FD_UNLIKELY( !strcmp( obj->name, "cnc" ) ) ) {
256 0 : return fd_cnc_footprint( 0UL );
257 228 : } else if( FD_UNLIKELY( !strcmp( obj->name, "fseq" ) ) ) {
258 159 : return fd_fseq_footprint();
259 159 : } else if( FD_UNLIKELY( !strcmp( obj->name, "metrics" ) ) ) {
260 69 : return FD_METRICS_FOOTPRINT( VAL("in_cnt"), VAL("cons_cnt") );
261 69 : } else if( FD_UNLIKELY( !strcmp( obj->name, "blockstore" ) ) ) {
262 0 : return fd_blockstore_footprint( VAL("shred_max"), VAL("block_max"), VAL("idx_max"), VAL("txn_max") ) + VAL("alloc_max");
263 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "funk" ) ) ) {
264 0 : return fd_funk_footprint();
265 0 : } else if( FD_UNLIKELY( !strcmp( obj->name, "txncache" ) ) ) {
266 0 : return fd_txncache_footprint( VAL("max_rooted_slots"), VAL("max_live_slots"), VAL("max_txn_per_slot"), FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS );
267 0 : } else {
268 0 : FD_LOG_ERR(( "unknown object `%s`", obj->name ));
269 0 : return 0UL;
270 0 : }
271 495 : #undef VAL
272 495 : }
273 :
274 : ulong
275 : fdctl_obj_loose( fd_topo_t const * topo,
276 495 : fd_topo_obj_t const * obj ) {
277 495 : ulong loose = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.%s", obj->id, "loose" );
278 495 : if( loose!=ULONG_MAX ) {
279 0 : return loose;
280 0 : }
281 :
282 495 : if( FD_UNLIKELY( !strcmp( obj->name, "tile" ) ) ) {
283 69 : fd_topo_tile_t const * tile = NULL;
284 828 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
285 828 : if( FD_LIKELY( topo->tiles[ i ].tile_obj_id==obj->id ) ) {
286 69 : tile = &topo->tiles[ i ];
287 69 : break;
288 69 : }
289 828 : }
290 69 : fd_topo_run_tile_t * config = fd_topo_tile_to_config( tile );
291 69 : if( FD_LIKELY( config->loose_footprint ) ) return config->loose_footprint( tile );
292 69 : }
293 492 : return 0UL;
294 495 : }
295 :
296 : fd_topo_run_tile_t
297 0 : fdctl_tile_run( fd_topo_tile_t * tile ) {
298 0 : return *fd_topo_tile_to_config( tile );
299 0 : }
300 :
301 :
302 : static void
303 3 : validate_ports( config_t * result ) {
304 3 : char dynamic_port_range[ 32 ];
305 3 : fd_memcpy( dynamic_port_range, result->dynamic_port_range, sizeof(dynamic_port_range) );
306 :
307 3 : char * dash = strstr( dynamic_port_range, "-" );
308 3 : if( FD_UNLIKELY( !dash ) )
309 0 : FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
310 3 : "This must be formatted like `<min>-<max>`",
311 3 : result->dynamic_port_range ));
312 :
313 3 : *dash = '\0';
314 3 : char * endptr;
315 3 : ulong agave_port_min = strtoul( dynamic_port_range, &endptr, 10 );
316 3 : if( FD_UNLIKELY( *endptr != '\0' || agave_port_min > USHORT_MAX ) )
317 0 : FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
318 3 : "This must be formatted like `<min>-<max>`",
319 3 : result->dynamic_port_range ));
320 3 : ulong agave_port_max = strtoul( dash + 1, &endptr, 10 );
321 3 : if( FD_UNLIKELY( *endptr != '\0' || agave_port_max > USHORT_MAX ) )
322 0 : FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
323 3 : "This must be formatted like `<min>-<max>`",
324 3 : result->dynamic_port_range ));
325 3 : if( FD_UNLIKELY( agave_port_min > agave_port_max ) )
326 0 : FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
327 3 : "The minimum port must be less than or equal to the maximum port",
328 3 : result->dynamic_port_range ));
329 :
330 3 : if( FD_UNLIKELY( result->tiles.quic.regular_transaction_listen_port >= agave_port_min &&
331 3 : result->tiles.quic.regular_transaction_listen_port < agave_port_max ) )
332 0 : FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.transaction_listen_port] `%hu`. "
333 3 : "This must be outside the dynamic port range `%s`",
334 3 : result->tiles.quic.regular_transaction_listen_port,
335 3 : result->dynamic_port_range ));
336 :
337 3 : if( FD_UNLIKELY( result->tiles.quic.quic_transaction_listen_port >= agave_port_min &&
338 3 : result->tiles.quic.quic_transaction_listen_port < agave_port_max ) )
339 0 : FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.quic_transaction_listen_port] `%hu`. "
340 3 : "This must be outside the dynamic port range `%s`",
341 3 : result->tiles.quic.quic_transaction_listen_port,
342 3 : result->dynamic_port_range ));
343 :
344 3 : if( FD_UNLIKELY( result->tiles.shred.shred_listen_port >= agave_port_min &&
345 3 : result->tiles.shred.shred_listen_port < agave_port_max ) )
346 0 : FD_LOG_ERR(( "configuration specifies invalid [tiles.shred.shred_listen_port] `%hu`. "
347 3 : "This must be outside the dynamic port range `%s`",
348 3 : result->tiles.shred.shred_listen_port,
349 3 : result->dynamic_port_range ));
350 3 : }
351 :
352 : /* These CLUSTER_* values must be ordered from least important to most
353 : important network. Eg, it's important that if a config has the
354 : MAINNET_BETA genesis hash, but has a bunch of entrypoints that we
355 : recognize as TESTNET, we classify it as MAINNET_BETA so we can be
356 : maximally restrictive. This is done by a high-to-low comparison. */
357 9 : #define FD_CONFIG_CLUSTER_UNKNOWN (0UL)
358 0 : #define FD_CONFIG_CLUSTER_PYTHTEST (1UL)
359 0 : #define FD_CONFIG_CLUSTER_TESTNET (2UL)
360 0 : #define FD_CONFIG_CLUSTER_DEVNET (3UL)
361 0 : #define FD_CONFIG_CLUSTER_PYTHNET (4UL)
362 0 : #define FD_CONFIG_CLUSTER_MAINNET_BETA (5UL)
363 :
364 : FD_FN_PURE static ulong
365 3 : determine_cluster( char * expected_genesis_hash ) {
366 3 : char const * DEVNET_GENESIS_HASH = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
367 3 : char const * TESTNET_GENESIS_HASH = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY";
368 3 : char const * MAINNET_BETA_GENESIS_HASH = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
369 3 : char const * PYTHTEST_GENESIS_HASH = "EkCkB7RWVrgkcpariRpd3pjf7GwiCMZaMHKUpB5Na1Ve";
370 3 : char const * PYTHNET_GENESIS_HASH = "GLKkBUr6r72nBtGrtBPJLRqtsh8wXZanX4xfnqKnWwKq";
371 :
372 3 : ulong cluster = FD_CONFIG_CLUSTER_UNKNOWN;
373 3 : if( FD_LIKELY( expected_genesis_hash ) ) {
374 3 : if( FD_UNLIKELY( !strcmp( expected_genesis_hash, DEVNET_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_DEVNET;
375 3 : else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, TESTNET_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_TESTNET;
376 3 : else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, MAINNET_BETA_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_MAINNET_BETA;
377 3 : else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, PYTHTEST_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_PYTHTEST;
378 3 : else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, PYTHNET_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_PYTHNET;
379 3 : }
380 :
381 3 : return cluster;
382 3 : }
383 :
384 : FD_FN_CONST static int
385 9 : parse_log_level( char const * level ) {
386 9 : if( FD_UNLIKELY( !strcmp( level, "DEBUG" ) ) ) return 0;
387 9 : if( FD_UNLIKELY( !strcmp( level, "INFO" ) ) ) return 1;
388 6 : if( FD_UNLIKELY( !strcmp( level, "NOTICE" ) ) ) return 2;
389 3 : if( FD_UNLIKELY( !strcmp( level, "WARNING" ) ) ) return 3;
390 0 : if( FD_UNLIKELY( !strcmp( level, "ERR" ) ) ) return 4;
391 0 : if( FD_UNLIKELY( !strcmp( level, "CRIT" ) ) ) return 5;
392 0 : if( FD_UNLIKELY( !strcmp( level, "ALERT" ) ) ) return 6;
393 0 : if( FD_UNLIKELY( !strcmp( level, "EMERG" ) ) ) return 7;
394 0 : return -1;
395 0 : }
396 :
397 : FD_FN_CONST static char *
398 3 : cluster_to_cstr( ulong cluster ) {
399 3 : switch( cluster ) {
400 3 : case FD_CONFIG_CLUSTER_UNKNOWN: return "unknown";
401 0 : case FD_CONFIG_CLUSTER_PYTHTEST: return "pythtest";
402 0 : case FD_CONFIG_CLUSTER_TESTNET: return "testnet";
403 0 : case FD_CONFIG_CLUSTER_DEVNET: return "devnet";
404 0 : case FD_CONFIG_CLUSTER_PYTHNET: return "pythnet";
405 0 : case FD_CONFIG_CLUSTER_MAINNET_BETA: return "mainnet-beta";
406 0 : default: return "unknown";
407 3 : }
408 3 : }
409 :
410 : static char *
411 3 : default_user( void ) {
412 3 : char * name = getenv( "SUDO_USER" );
413 3 : if( FD_UNLIKELY( name ) ) return name;
414 :
415 0 : name = getenv( "LOGNAME" );
416 0 : if( FD_LIKELY( name ) ) return name;
417 :
418 0 : name = getenv( "USER" );
419 0 : if( FD_LIKELY( name ) ) return name;
420 :
421 0 : name = getenv( "LNAME" );
422 0 : if( FD_LIKELY( name ) ) return name;
423 :
424 0 : name = getenv( "USERNAME" );
425 0 : if( FD_LIKELY( name ) ) return name;
426 :
427 0 : name = getlogin();
428 0 : if( FD_UNLIKELY( !name && (errno==ENXIO || errno==ENOTTY) ) ) return NULL;
429 0 : else if( FD_UNLIKELY( !name ) ) FD_LOG_ERR(( "getlogin failed (%i-%s)", errno, fd_io_strerror( errno ) ));
430 0 : return name;
431 0 : }
432 :
433 : void
434 : fdctl_cfg_from_env( int * pargc,
435 : char *** pargv,
436 3 : config_t * config ) {
437 :
438 3 : memset( config, 0, sizeof(config_t) );
439 : #if FD_HAS_NO_AGAVE
440 : static uchar pod_mem1[ 1UL<<20 ];
441 : static uchar pod_mem2[ 1UL<<20 ];
442 : uchar * pod1 = fd_pod_join( fd_pod_new( pod_mem1, sizeof(pod_mem1) ) );
443 : uchar * pod2 = fd_pod_join( fd_pod_new( pod_mem2, sizeof(pod_mem2) ) );
444 :
445 : uchar scratch[ 4096 ];
446 : long toml_err = fd_toml_parse( fdctl_default_config, fdctl_default_config_sz, pod1, scratch, sizeof(scratch) );
447 : if( FD_UNLIKELY( toml_err!=FD_TOML_SUCCESS ) ) FD_LOG_ERR(( "Invalid config (%s)", "default.toml" ));
448 : toml_err = fd_toml_parse( fdctl_default_firedancer_config, fdctl_default_firedancer_config_sz, pod2, scratch, sizeof(scratch) );
449 : if( FD_UNLIKELY( toml_err!=FD_TOML_SUCCESS ) ) FD_LOG_ERR(( "Invalid config (%s)", "default-firedancer.toml" ));
450 :
451 : if( FD_UNLIKELY( !fdctl_pod_to_cfg( config, pod1 ) ) ) FD_LOG_ERR(( "Invalid config (%s)", "default.toml" ));
452 : if( FD_UNLIKELY( !fdctl_pod_to_cfg( config, pod2 ) ) ) FD_LOG_ERR(( "Invalid config (%s)", "default-firedancer.toml" ));
453 : fd_pod_delete( fd_pod_leave( pod1 ) );
454 : fd_pod_delete( fd_pod_leave( pod2 ) );
455 : #else
456 3 : fdctl_cfg_load_buf( config, (char const *)fdctl_default_config, fdctl_default_config_sz, "default" );
457 3 : #endif
458 :
459 3 : const char * user_config = fd_env_strip_cmdline_cstr(
460 3 : pargc,
461 3 : pargv,
462 3 : "--config",
463 3 : "FIREDANCER_CONFIG_TOML",
464 3 : NULL );
465 :
466 3 : if( FD_LIKELY( user_config ) ) {
467 0 : fdctl_cfg_load_file( config, user_config );
468 0 : }
469 :
470 3 : int netns = fd_env_strip_cmdline_contains( pargc, pargv, "--netns" );
471 3 : if( FD_UNLIKELY( netns ) ) {
472 0 : config->development.netns.enabled = 1;
473 0 : strncpy( config->tiles.net.interface,
474 0 : config->development.netns.interface0,
475 0 : sizeof(config->tiles.net.interface) );
476 0 : config->tiles.net.interface[ sizeof(config->tiles.net.interface) - 1 ] = '\0';
477 0 : }
478 :
479 3 : if( FD_UNLIKELY( !strcmp( config->user, "" ) ) ) {
480 3 : const char * user = default_user();
481 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." ));
482 3 : if( FD_UNLIKELY( strlen( user ) >= sizeof( config->user ) ) )
483 0 : FD_LOG_ERR(( "user name `%s` is too long", user ));
484 3 : strncpy( config->user, user, 256 );
485 3 : }
486 :
487 3 : struct utsname utsname;
488 3 : if( FD_UNLIKELY( -1==uname( &utsname ) ) )
489 0 : FD_LOG_ERR(( "could not get uname (%i-%s)", errno, fd_io_strerror( errno ) ));
490 3 : strncpy( config->hostname, utsname.nodename, sizeof(config->hostname) );
491 3 : config->hostname[ sizeof(config->hostname)-1UL ] = '\0'; /* Just truncate the name if it's too long to fit */
492 :
493 3 : if( FD_UNLIKELY( !strcmp( config->tiles.net.interface, "" ) && !config->development.netns.enabled ) ) {
494 3 : int ifindex = internet_routing_interface();
495 3 : if( FD_UNLIKELY( ifindex == -1 ) )
496 0 : FD_LOG_ERR(( "no network device found which routes to 8.8.8.8. If no network "
497 3 : "interface is specified in the configuration file, Firedancer "
498 3 : "tries to use the first network interface found which routes to "
499 3 : "8.8.8.8. You can see what this is by running `ip route get 8.8.8.8` "
500 3 : "You can fix this error by specifying a network interface to bind to in "
501 3 : "your configuration file under [net.interface]" ));
502 :
503 3 : if( FD_UNLIKELY( !if_indextoname( (uint)ifindex, config->tiles.net.interface ) ) )
504 0 : FD_LOG_ERR(( "could not get name of interface with index %d", ifindex ));
505 3 : }
506 :
507 3 : ulong cluster = determine_cluster( config->consensus.expected_genesis_hash );
508 3 : config->is_live_cluster = cluster != FD_CONFIG_CLUSTER_UNKNOWN;
509 :
510 3 : if( FD_UNLIKELY( config->development.netns.enabled ) ) {
511 : /* not currently supporting multihoming on netns */
512 0 : if( FD_UNLIKELY( strcmp( config->development.netns.interface0, config->tiles.net.interface ) ) )
513 0 : FD_LOG_ERR(( "netns interface and firedancer interface are different. If you are using the "
514 0 : "[development.netns] functionality to run Firedancer in a network namespace "
515 0 : "for development, the configuration file must specify that "
516 0 : "[development.netns.interface0] is the same as [net.interface]" ));
517 :
518 0 : if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->development.netns.interface0_addr, &config->tiles.net.ip_addr ) ) )
519 0 : FD_LOG_ERR(( "configuration specifies invalid netns IP address `%s`", config->development.netns.interface0_addr ));
520 0 : if( FD_UNLIKELY( !fd_cstr_to_mac_addr( config->development.netns.interface0_mac, config->tiles.net.mac_addr ) ) )
521 0 : FD_LOG_ERR(( "configuration specifies invalid netns MAC address `%s`", config->development.netns.interface0_mac ));
522 3 : } else {
523 3 : if( FD_UNLIKELY( !if_nametoindex( config->tiles.net.interface ) ) )
524 0 : FD_LOG_ERR(( "configuration specifies network interface `%s` which does not exist", config->tiles.net.interface ));
525 3 : uint iface_ip = listen_address( config->tiles.net.interface );
526 3 : if( FD_UNLIKELY( strcmp( config->gossip.host, "" ) ) ) {
527 0 : uint gossip_ip_addr = iface_ip;
528 0 : int has_gossip_ip4 = 0;
529 0 : if( FD_UNLIKELY( strlen( config->gossip.host )<=15UL ) ) {
530 : /* Only sets gossip_ip_addr if it's a valid IPv4 address, otherwise assume it's a DNS name */
531 0 : has_gossip_ip4 = fd_cstr_to_ip4_addr( config->gossip.host, &gossip_ip_addr );
532 0 : }
533 0 : if ( FD_UNLIKELY( !fd_ip4_addr_is_public( gossip_ip_addr ) && config->is_live_cluster && has_gossip_ip4 ) )
534 0 : FD_LOG_ERR(( "Trying to use [gossip.host] " FD_IP4_ADDR_FMT " for listening to incoming "
535 0 : "transactions, but it is part of a private network and will not be routable "
536 0 : "for other Solana network nodes.",
537 0 : FD_IP4_ADDR_FMT_ARGS( iface_ip ) ));
538 3 : } else if ( FD_UNLIKELY( !fd_ip4_addr_is_public( iface_ip ) && config->is_live_cluster ) ) {
539 0 : FD_LOG_ERR(( "Trying to use network interface `%s` for listening to incoming transactions, "
540 0 : "but it has IPv4 address " FD_IP4_ADDR_FMT " which is part of a private network "
541 0 : "and will not be routable for other Solana network nodes. If you are running "
542 0 : "behind a NAT and this interface is publicly reachable, you can continue by "
543 0 : "manually specifying the IP address to advertise in your configuration under "
544 0 : "[gossip.host].",
545 0 : config->tiles.net.interface, FD_IP4_ADDR_FMT_ARGS( iface_ip ) ));
546 0 : }
547 :
548 3 : config->tiles.net.ip_addr = iface_ip;
549 3 : mac_address( config->tiles.net.interface, config->tiles.net.mac_addr );
550 :
551 : /* support for multihomed hosts */
552 3 : ulong multi_cnt = config->tiles.net.multihome_ip_addrs_cnt;
553 3 : for( ulong j = 0; j < multi_cnt; ++j ) {
554 0 : int success = fd_cstr_to_ip4_addr( config->tiles.net.multihome_ip_addrs[j],
555 0 : &config->tiles.net.multihome_ip4_addrs[j] );
556 0 : if( !success ) {
557 0 : FD_LOG_ERR(( "configuration option [tiles.net.multihome_ip_addrs] "
558 0 : "specifies malformed IP address `%s`",
559 0 : config->tiles.net.multihome_ip_addrs[j] ));
560 0 : }
561 0 : }
562 :
563 : /* look for duplicate addresses */
564 : /* there's only a few, so do the O(n^2) comparison */
565 3 : for( ulong j = 0; j < multi_cnt; ++j ) {
566 0 : if( config->tiles.net.ip_addr == config->tiles.net.multihome_ip4_addrs[j] ) {
567 0 : FD_LOG_ERR(( "configuration option [tiles.net.multihome_ip_addrs] "
568 0 : "specifies an address that matches [tiles.net.src_ip_addr]" ));
569 0 : }
570 0 : for( ulong k = j+1; k < multi_cnt; ++k ) {
571 0 : if( config->tiles.net.multihome_ip4_addrs[j] == config->tiles.net.multihome_ip4_addrs[k] ) {
572 0 : FD_LOG_ERR(( "configuration option [tiles.net.multihome_ip_addrs] "
573 0 : "specifies duplicate ip addresses `%s`",
574 0 : config->tiles.net.multihome_ip_addrs[j] ));
575 0 : }
576 0 : }
577 0 : }
578 :
579 3 : }
580 :
581 3 : username_to_id( config );
582 :
583 3 : if( config->uid == 0 || config->gid == 0 )
584 0 : FD_LOG_ERR(( "firedancer cannot run as root. please specify a non-root user in the configuration file" ));
585 :
586 3 : if( FD_UNLIKELY( getuid() != 0 && config->uid != getuid() ) )
587 0 : FD_LOG_ERR(( "running as uid %u, but config specifies uid %u", getuid(), config->uid ));
588 3 : if( FD_UNLIKELY( getgid() != 0 && config->gid != getgid() ) )
589 0 : FD_LOG_ERR(( "running as gid %u, but config specifies gid %u", getgid(), config->gid ));
590 :
591 3 : ulong len = strlen( config->hugetlbfs.mount_path );
592 3 : if( FD_UNLIKELY( !len ) ) FD_LOG_ERR(( "[hugetlbfs.mount_path] must be non-empty in your configuration file" ));
593 3 : FD_TEST( fd_cstr_printf_check( config->hugetlbfs.gigantic_page_mount_path,
594 3 : sizeof(config->hugetlbfs.gigantic_page_mount_path),
595 3 : NULL,
596 3 : "%s/.gigantic",
597 3 : config->hugetlbfs.mount_path ) );
598 3 : FD_TEST( fd_cstr_printf_check( config->hugetlbfs.huge_page_mount_path,
599 3 : sizeof(config->hugetlbfs.huge_page_mount_path),
600 3 : NULL,
601 3 : "%s/.huge",
602 3 : config->hugetlbfs.mount_path ) );
603 :
604 3 : replace( config->log.path, "{user}", config->user );
605 3 : replace( config->log.path, "{name}", config->name );
606 3 : if( FD_LIKELY( !strcmp( "auto", config->log.colorize ) ) ) config->log.colorize1 = 2;
607 0 : else if( FD_LIKELY( !strcmp( "true", config->log.colorize ) ) ) config->log.colorize1 = 1;
608 0 : else if( FD_LIKELY( !strcmp( "false", config->log.colorize ) ) ) config->log.colorize1 = 0;
609 0 : else FD_LOG_ERR(( "[log.colorize] must be one of \"auto\", \"true\", or \"false\"" ));
610 :
611 3 : if( FD_LIKELY( 2==config->log.colorize1 ) ) {
612 3 : char const * cstr = fd_env_strip_cmdline_cstr( NULL, NULL, NULL, "COLORTERM", NULL );
613 3 : int truecolor = cstr && !strcmp( cstr, "truecolor" );
614 :
615 3 : cstr = fd_env_strip_cmdline_cstr( NULL, NULL, NULL, "TERM", NULL );
616 3 : int xterm256color = cstr && !strcmp( cstr, "xterm-256color" );
617 :
618 3 : config->log.colorize1 = truecolor || xterm256color;
619 3 : }
620 :
621 3 : config->log.level_logfile1 = parse_log_level( config->log.level_logfile );
622 3 : config->log.level_stderr1 = parse_log_level( config->log.level_stderr );
623 3 : config->log.level_flush1 = parse_log_level( config->log.level_flush );
624 3 : if( FD_UNLIKELY( -1==config->log.level_logfile1 ) ) FD_LOG_ERR(( "unrecognized [log.level_logfile] `%s`", config->log.level_logfile ));
625 3 : if( FD_UNLIKELY( -1==config->log.level_stderr1 ) ) FD_LOG_ERR(( "unrecognized [log.level_stderr] `%s`", config->log.level_logfile ));
626 3 : if( FD_UNLIKELY( -1==config->log.level_flush1 ) ) FD_LOG_ERR(( "unrecognized [log.level_flush] `%s`", config->log.level_logfile ));
627 :
628 3 : replace( config->scratch_directory, "{user}", config->user );
629 3 : replace( config->scratch_directory, "{name}", config->name );
630 :
631 3 : if( FD_UNLIKELY( strcmp( config->ledger.path, "" ) ) ) {
632 0 : replace( config->ledger.path, "{user}", config->user );
633 0 : replace( config->ledger.path, "{name}", config->name );
634 3 : } else {
635 3 : FD_TEST( fd_cstr_printf_check( config->ledger.path, sizeof(config->ledger.path), NULL, "%s/ledger", config->scratch_directory ) );
636 3 : }
637 :
638 3 : if( FD_UNLIKELY( strcmp( config->snapshots.path, "" ) ) ) {
639 0 : replace( config->snapshots.path, "{user}", config->user );
640 0 : replace( config->snapshots.path, "{name}", config->name );
641 3 : } else {
642 3 : strncpy( config->snapshots.path, config->ledger.path, sizeof(config->snapshots.path) );
643 3 : }
644 :
645 3 : if( FD_UNLIKELY( !strcmp( config->consensus.identity_path, "" ) ) ) {
646 3 : FD_TEST( fd_cstr_printf_check( config->consensus.identity_path,
647 3 : sizeof(config->consensus.identity_path),
648 3 : NULL,
649 3 : "%s/identity.json",
650 3 : config->scratch_directory ) );
651 3 : } else {
652 0 : replace( config->consensus.identity_path, "{user}", config->user );
653 0 : replace( config->consensus.identity_path, "{name}", config->name );
654 0 : }
655 :
656 : #if FD_HAS_NO_AGAVE
657 : if( FD_UNLIKELY( !strcmp( config->consensus.vote_account_path, "" ) ) ) {
658 : FD_TEST( fd_cstr_printf_check( config->consensus.vote_account_path,
659 : sizeof(config->consensus.vote_account_path),
660 : NULL,
661 : "%s/vote-account.json",
662 : config->scratch_directory ) );
663 : }
664 : #endif
665 3 : replace( config->consensus.vote_account_path, "{user}", config->user );
666 3 : replace( config->consensus.vote_account_path, "{name}", config->name );
667 :
668 3 : for( ulong i=0UL; i<config->consensus.authorized_voter_paths_cnt; i++ ) {
669 0 : replace( config->consensus.authorized_voter_paths[ i ], "{user}", config->user );
670 0 : replace( config->consensus.authorized_voter_paths[ i ], "{name}", config->name );
671 0 : }
672 :
673 3 : strcpy( config->cluster, cluster_to_cstr( cluster ) );
674 :
675 : #ifdef FD_HAS_NO_AGAVE
676 : if( FD_UNLIKELY( config->is_live_cluster && cluster!=FD_CONFIG_CLUSTER_TESTNET ) )
677 : FD_LOG_ERR(( "Attempted to start against live cluster `%s`. Firedancer is not "
678 : "ready for production deployment, has not been tested, and is "
679 : "missing consensus critical functionality. Joining a live Solana "
680 : "cluster may destabilize the network. Please do not attempt. You "
681 : "can start against the testnet cluster by specifying the testnet "
682 : "entrypoints from https://docs.solana.com/clusters under "
683 : "[gossip.entrypoints] in your configuration file.", cluster_to_cstr( cluster ) ));
684 : #endif
685 :
686 3 : if( FD_LIKELY( config->is_live_cluster) ) {
687 0 : if( FD_UNLIKELY( !config->development.sandbox ) )
688 0 : FD_LOG_ERR(( "trying to join a live cluster, but configuration disables the sandbox which is a a development only feature" ));
689 0 : if( FD_UNLIKELY( config->development.no_clone ) )
690 0 : FD_LOG_ERR(( "trying to join a live cluster, but configuration disables multiprocess which is a development only feature" ));
691 0 : if( FD_UNLIKELY( config->development.netns.enabled ) )
692 0 : FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.netns] which is a development only feature" ));
693 0 : if( FD_UNLIKELY( config->development.bench.larger_max_cost_per_block ) )
694 0 : 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" ));
695 0 : if( FD_UNLIKELY( config->development.bench.larger_shred_limits_per_block ) )
696 0 : 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" ));
697 0 : if( FD_UNLIKELY( config->development.bench.disable_blockstore_from_slot ) )
698 0 : 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" ));
699 0 : if( FD_UNLIKELY( config->development.bench.disable_status_cache ) )
700 0 : FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.bench.disable_status_cache] which is a development only feature" ));
701 0 : }
702 :
703 3 : if( FD_UNLIKELY( config->tiles.quic.quic_transaction_listen_port != config->tiles.quic.regular_transaction_listen_port + 6 ) )
704 0 : FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.quic_transaction_listen_port] `%hu`. "
705 3 : "This must be 6 more than [tiles.quic.regular_transaction_listen_port] `%hu`",
706 3 : config->tiles.quic.quic_transaction_listen_port,
707 3 : config->tiles.quic.regular_transaction_listen_port ));
708 :
709 3 : if( FD_LIKELY( !strcmp( config->consensus.identity_path, "" ) ) ) {
710 0 : if( FD_UNLIKELY( config->is_live_cluster ) )
711 0 : FD_LOG_ERR(( "configuration file must specify [consensus.identity_path] when joining a live cluster" ));
712 :
713 0 : FD_TEST( fd_cstr_printf_check( config->consensus.identity_path,
714 0 : sizeof(config->consensus.identity_path),
715 0 : NULL,
716 0 : "%s/identity.json",
717 0 : config->scratch_directory ) );
718 0 : }
719 :
720 3 : fdctl_cfg_validate( config );
721 3 : validate_ports( config );
722 3 : fd_topo_initialize( config );
723 3 : }
724 :
725 : int
726 0 : fdctl_cfg_to_memfd( config_t * config ) {
727 0 : int config_memfd = memfd_create( "fd_config", 0 );
728 0 : if( FD_UNLIKELY( -1==config_memfd ) ) FD_LOG_ERR(( "memfd_create() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
729 0 : if( FD_UNLIKELY( -1==ftruncate( config_memfd, sizeof( config_t ) ) ) ) FD_LOG_ERR(( "ftruncate() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
730 :
731 0 : uchar * bytes = mmap( NULL, sizeof( config_t ), PROT_READ | PROT_WRITE, MAP_SHARED, config_memfd, 0 );
732 0 : if( FD_UNLIKELY( bytes == MAP_FAILED ) ) FD_LOG_ERR(( "mmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
733 0 : fd_memcpy( bytes, config, sizeof( config_t ) );
734 0 : if( FD_UNLIKELY( munmap( bytes, sizeof( config_t ) ) ) ) FD_LOG_ERR(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
735 :
736 0 : return config_memfd;
737 0 : }
|