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