diff --git a/src/app/firedancer-dev/commands/send_test/Local.mk b/src/app/firedancer-dev/commands/send_test/Local.mk new file mode 100644 index 00000000000..1a74274bac4 --- /dev/null +++ b/src/app/firedancer-dev/commands/send_test/Local.mk @@ -0,0 +1,3 @@ +ifdef FD_HAS_INT128 +$(call add-objs,send_test,fd_firedancer_dev) +endif diff --git a/src/app/firedancer-dev/commands/send_test/send_test.c b/src/app/firedancer-dev/commands/send_test/send_test.c new file mode 100644 index 00000000000..48f7fe78e39 --- /dev/null +++ b/src/app/firedancer-dev/commands/send_test/send_test.c @@ -0,0 +1,278 @@ +/* +send_test is a firedancer-dev command that tests the send tile. +It uses the net, send, metrics, and sign tiles, just like in prod. +The main test function writes contact info to the gossip_send link, +stake info to the stake_out link, and triggers mock votes on the +tower_send link. + +It takes two required arguments: +--gossip-file: the path to the gossip file +--stake-file: the path to the stake file +These two files should include lines from the 'solana gossip' and +'solana validators' commands, respectively. It is recommended to run +with a known good subset of nodes while tuning the send tile. +*/ +#include "../../../shared/commands/configure/configure.h" +#include "../../../shared/commands/run/run.h" /* initialize_workspaces */ +#include "../../../shared/fd_config.h" /* config_t */ +#include "../../../../disco/topo/fd_topob.h" +#include "../../../../disco/topo/fd_cpu_topo.h" /* fd_topo_cpus_t */ +#include "../../../../util/tile/fd_tile_private.h" +#include "../../../../disco/net/fd_net_tile.h" /* fd_topos_net_tiles */ +#include "../../../../flamenco/leaders/fd_leaders_base.h" /* FD_STAKE_OUT_MTU */ +#include "../../../../disco/pack/fd_microblock.h" /* fd_txn_p_t */ +#include "../../../../app/firedancer/topology.h" /* fd_topo_configure_tile */ +#include "../../../../disco/keyguard/fd_keyload.h" + +#include "send_test_helpers.c" + +extern fd_topo_obj_callbacks_t * CALLBACKS[]; + +fd_topo_run_tile_t +fdctl_tile_run( fd_topo_tile_t const * tile ); + +static void +send_test_topo( config_t * config ) { + + ulong const net_tile_cnt = config->layout.net_tile_count; + ulong const ingress_buf_sz = config->net.ingress_buffer_size; + + /* Setup topology */ + fd_topo_t * topo = fd_topob_new( &config->topo, config->name ); + topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size ); + + /* tile wksps */ + fd_topob_wksp( topo, "metric_in" ); + fd_topob_wksp( topo, "metric" ); + fd_topob_wksp( topo, "sign" ); + fd_topob_wksp( topo, "send" ); + + /* wksps for real links */ + fd_topob_wksp( topo, "send_net" ); + fd_topob_wksp( topo, "sign_send" ); + fd_topob_wksp( topo, "send_sign" ); + + /* wksps for mock links */ + fd_topob_wksp( topo, "gossip_send" ); + fd_topob_wksp( topo, "stake_out" ); + fd_topob_wksp( topo, "tower_send" ); + fd_topob_wksp( topo, "send_txns" ); + + ulong tile_to_cpu[ FD_TILE_MAX ] = {0}; + ushort parsed_tile_to_cpu[ FD_TILE_MAX ]; + for( ulong i=0UL; ilayout.affinity, "auto" ) ) ) affinity_tile_cnt = fd_tile_private_cpus_parse( config->layout.affinity, parsed_tile_to_cpu ); + + for( ulong i=0UL; i=cpus->cpu_cnt ) ) + FD_LOG_ERR(( "The CPU affinity string in the configuration file under [layout.affinity] specifies a CPU index of %hu, but the system " + "only has %lu CPUs. You should either change the CPU allocations in the affinity string, or increase the number of CPUs " + "in the system.", + parsed_tile_to_cpu[ i ], cpus->cpu_cnt )); + tile_to_cpu[ i ] = fd_ulong_if( parsed_tile_to_cpu[ i ]==USHORT_MAX, ULONG_MAX, (ulong)parsed_tile_to_cpu[ i ] ); + } + + #define FOR(cnt) for( ulong i=0UL; inet, config->tiles.netlink.max_routes, config->tiles.netlink.max_peer_routes, config->tiles.netlink.max_neighbors, tile_to_cpu ); + fd_topob_tile( topo, "metric", "metric", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0 ); + fd_topob_tile( topo, "send", "send", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0 ); + fd_topob_tile( topo, "sign", "sign", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 1 ); + + /* real links */ + FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_send", i, ingress_buf_sz ); + + FOR(net_tile_cnt) fd_topob_link( topo, "send_net", "send_net", ingress_buf_sz, FD_NET_MTU, 1UL ); + /**/ fd_topob_link( topo, "send_sign", "send_sign", 128UL, FD_TXN_MTU, 1UL ); + /**/ fd_topob_link( topo, "sign_send", "sign_send", 128UL, 64UL, 1UL ); + + /* mock links */ + fd_topob_link( topo, "gossip_send", "gossip_send", 128UL, 40200UL * 38UL, 1UL ) + ->permit_no_producers = 1; + fd_topob_link( topo, "stake_out", "stake_out", 128UL, FD_STAKE_OUT_MTU, 1UL ) + ->permit_no_producers = 1; + fd_topob_link( topo, "tower_send", "tower_send", 65536UL, sizeof(fd_txn_p_t), 1UL ) + ->permit_no_producers = 1; + fd_topob_link( topo, "send_txns", "send_txns", 128UL, 40200UL * 38UL, 1UL ) + ->permit_no_consumers = 1; + + /* attach mock links */ + fd_topob_tile_in( topo, "send", 0UL, "metric_in", "gossip_send", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + fd_topob_tile_in( topo, "send", 0UL, "metric_in", "stake_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + fd_topob_tile_in( topo, "send", 0UL, "metric_in", "tower_send", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + + /* attach real links */ + fd_topos_tile_in_net( topo, "metric_in", "send_net", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); + fd_topob_tile_in ( topo, "send", 0UL, "metric_in", "net_send", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); + + fd_topob_tile_out( topo, "send", 0UL, "send_net", 0UL ); + + /* unpolled links have to be last! */ + fd_topob_tile_in ( topo, "sign", 0UL, "metric_in", "send_sign", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + fd_topob_tile_in ( topo, "send", 0UL, "metric_in", "sign_send", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_UNPOLLED ); + fd_topob_tile_out( topo, "send", 0UL, "send_sign", 0UL ); + fd_topob_tile_out( topo, "sign", 0UL, "sign_send", 0UL ); + fd_topob_tile_out( topo, "send", 0UL, "send_txns", 0UL ); + + FOR(net_tile_cnt) fd_topos_net_tile_finish( topo, i ); + + for( ulong i=0UL; itile_cnt; i++ ) { + fd_topo_tile_t * tile = &topo->tiles[ i ]; + if( !fd_topo_configure_tile( tile, config ) ) { + FD_LOG_ERR(( "unknown tile name %lu `%s`", i, tile->name )); + } + } + + /* Finish topology setup */ + if( FD_UNLIKELY( !strcmp( config->layout.affinity, "auto" ) ) ) fd_topob_auto_layout( topo, 0 ); + fd_topob_finish( topo, CALLBACKS ); +} + +struct { + char gossip_file[256]; + char stake_file[256]; +} send_test_args = {0}; + +static void +send_test_cmd_args( int * pargc, + char *** pargv, + args_t * args FD_PARAM_UNUSED ) { + char ** _pargv = *pargv; + int _pargc = *pargc; + int found_gossip = 0; + int found_stake = 0; + + /* Extract our arguments */ + for( int i = 0; i < _pargc - 1; i++ ) { + if( !strcmp( _pargv[i], "--gossip-file" ) ) { + strncpy( send_test_args.gossip_file, _pargv[i+1], sizeof(send_test_args.gossip_file) - 1 ); + found_gossip = 1; + } else if( !strcmp( _pargv[i], "--stake-file" ) ) { + strncpy( send_test_args.stake_file, _pargv[i+1], sizeof(send_test_args.stake_file) - 1 ); + found_stake = 1; + } + } + + /* Remove our arguments from argv */ + int write_idx = 0; + for( int read_idx = 0; read_idx < _pargc; read_idx++ ) { + if( read_idx < _pargc - 1 && + (!strcmp( _pargv[read_idx], "--gossip-file" ) || !strcmp( _pargv[read_idx], "--stake-file" )) ) { + read_idx++; /* Skip the argument value too */ + } else { + _pargv[write_idx++] = _pargv[read_idx]; + } + } + *pargc = write_idx; + + if( !found_gossip ) FD_LOG_ERR(( "--gossip-file is required" )); + if( !found_stake ) FD_LOG_ERR(( "--stake-file is required" )); +} + + +static void +init( send_test_ctx_t * ctx, config_t * config ) { + fd_topo_t * topo = &config->topo; + ctx->topo = topo; + ctx->config = config; + + /* Copy file paths from send_test_args */ + fd_memcpy( ctx->gossip_file, send_test_args.gossip_file, sizeof(ctx->gossip_file) ); + fd_memcpy( ctx->stake_file, send_test_args.stake_file, sizeof(ctx->stake_file ) ); + + ctx->identity_key [ 0 ] = *(fd_pubkey_t const *)(fd_keyload_load( config->paths.identity_key, /* pubkey only: */ 1 ) ); + ctx->vote_acct_addr[ 0 ] = *(fd_pubkey_t const *)(fd_keyload_load( config->paths.vote_account, /* pubkey only: */ 1 ) ); + + ctx->out_links[ MOCK_CI_IDX ] = setup_test_out_link( topo, "gossip_send" ); + ctx->out_links[ MOCK_STAKE_IDX ] = setup_test_out_link( topo, "stake_out" ); + ctx->out_links[ MOCK_TRIGGER_IDX ] = setup_test_out_link( topo, "tower_send" ); + + ctx->out_fns [ MOCK_CI_IDX ] = send_test_ci; + ctx->out_fns [ MOCK_STAKE_IDX ] = send_test_stake; + ctx->out_fns [ MOCK_TRIGGER_IDX ] = send_test_trigger; + + ctx->last_evt [ MOCK_CI_IDX ] = 0; + ctx->last_evt [ MOCK_STAKE_IDX ] = 0; + ctx->last_evt [ MOCK_TRIGGER_IDX ] = 0; + + ctx->delay [ MOCK_CI_IDX ] = 5e9L; + ctx->delay [ MOCK_STAKE_IDX ] = 172800e9L; + ctx->delay [ MOCK_TRIGGER_IDX ] = 400e6L; + + encode_vote( ctx, ctx->txn_buf ); + + /* send first epoch of stake info */ + send_test_stake( ctx, &ctx->out_links[ MOCK_STAKE_IDX ] ); +} +static void +send_test_main_loop( send_test_ctx_t * ctx ) { + for(;;) { + long now = fd_tickcount(); + for( ulong i=0UL; ilast_evt[ i ] + ctx->delay[ i ] <= now ) { + send_test_out_t * out = &ctx->out_links[ i ]; + ctx->out_fns [ i ]( ctx, out ); + ctx->last_evt[ i ] = now; + } + } + } +} + +static void +send_test_cmd_fn( args_t * args , + config_t * config ) { + send_test_topo( config ); + + configure_stage( &fd_cfg_stage_sysctl, CONFIGURE_CMD_INIT, config ); + configure_stage( &fd_cfg_stage_hugetlbfs, CONFIGURE_CMD_INIT, config ); + configure_stage( &fd_cfg_stage_ethtool_channels, CONFIGURE_CMD_INIT, config ); + configure_stage( &fd_cfg_stage_ethtool_gro, CONFIGURE_CMD_INIT, config ); + configure_stage( &fd_cfg_stage_ethtool_loopback, CONFIGURE_CMD_INIT, config ); + + fd_topo_print_log( 0, &config->topo ); + + run_firedancer_init( config, !args->dev.no_init_workspaces ); + fdctl_setup_netns( config, 1 ); + + if( 0==strcmp( config->net.provider, "xdp" ) ) fd_topo_install_xdp( &config->topo, config->net.bind_address_parsed ); + + fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_WRITE ); + fd_topo_run_single_process( &config->topo, 2, config->uid, config->gid, fdctl_tile_run ); + + send_test_ctx_t ctx = {0}; + init( &ctx, config ); + send_test_main_loop( &ctx ); +} + +static void +configure_stage_perm( configure_stage_t const * stage, + fd_cap_chk_t * chk, + config_t const * config ) { + int enabled = !stage->enabled || stage->enabled( config ); + if( enabled && stage->check( config ).result != CONFIGURE_OK ) + if( stage->init_perm ) stage->init_perm( chk, config ); +} + +static void +send_test_cmd_perm( args_t * args FD_PARAM_UNUSED, + fd_cap_chk_t * chk, + config_t const * config ) { + configure_stage_perm( &fd_cfg_stage_sysctl, chk, config ); + configure_stage_perm( &fd_cfg_stage_hugetlbfs, chk, config ); + configure_stage_perm( &fd_cfg_stage_ethtool_channels, chk, config ); + configure_stage_perm( &fd_cfg_stage_ethtool_gro, chk, config ); + configure_stage_perm( &fd_cfg_stage_ethtool_loopback, chk, config ); +} + +action_t fd_action_send_test = { + .name = "send_test", + .args = send_test_cmd_args, + .fn = send_test_cmd_fn, + .perm = send_test_cmd_perm, +}; diff --git a/src/app/firedancer-dev/commands/send_test/send_test_helpers.c b/src/app/firedancer-dev/commands/send_test/send_test_helpers.c new file mode 100644 index 00000000000..81559ce8f82 --- /dev/null +++ b/src/app/firedancer-dev/commands/send_test/send_test_helpers.c @@ -0,0 +1,232 @@ +#ifndef FD_SRC_APP_FIREDANCER_DEV_COMMANDS_SEND_TEST_HELPERS_C +#define FD_SRC_APP_FIREDANCER_DEV_COMMANDS_SEND_TEST_HELPERS_C + +#include "../../../../disco/fd_disco.h" +#include "../../../../choreo/tower/fd_tower.h" +#include "../../../../flamenco/leaders/fd_leaders_base.h" +#include "../../../../disco/pack/fd_microblock.h" +#include "../../../../util/net/fd_ip4.h" + +#include +#include +#include + +#define MOCK_CI_IDX 0UL +#define MOCK_STAKE_IDX 1UL +#define MOCK_TRIGGER_IDX 2UL +#define MOCK_CNT 3UL + +/* Forward declarations for send_test types */ +struct send_test_ctx; +typedef struct send_test_ctx send_test_ctx_t; + +typedef struct { + fd_frag_meta_t * mcache; + ulong * sync; + ulong depth; + ulong seq; + + fd_wksp_t * mem; + ulong chunk0; + ulong wmark; + ulong chunk; +} send_test_out_t; + +typedef void +(* out_fn_t)( send_test_ctx_t * ctx, send_test_out_t * out ); + +struct send_test_ctx { + fd_topo_t * topo; + fd_config_t * config; + + send_test_out_t out_links[ MOCK_CNT ]; + out_fn_t out_fns [ MOCK_CNT ]; + + fd_pubkey_t identity_key [ 1 ]; + fd_pubkey_t vote_acct_addr[ 1 ]; + + fd_txn_p_t txn_buf[ 1 ]; + + long last_evt[ MOCK_CNT ]; + long delay [ MOCK_CNT ]; + + ulong epoch; + ulong slot; + + char gossip_file[256]; + char stake_file[256]; +}; + +/* File paths are now part of the ctx struct */ + +/* Event handler function implementations */ + +static inline fd_shred_dest_wire_t +parse_gossip_line( char * line ) { + fd_shred_dest_wire_t dest; + FD_LOG_DEBUG(( "Parsing gossip line: %s", line )); + + /* Parse tokens */ + char * ip_token = strtok( line, " |\t" ); FD_TEST( ip_token ); + char * pubkey_token = strtok( NULL, " |\t" ); FD_TEST( pubkey_token ); + /* ************* */ strtok( NULL, " |\t" ); /* Skip port1 */ + /* ************* */ strtok( NULL, " |\t" ); /* Skip port2 */ + char * tpu_port = strtok( NULL, " |\t" ); FD_TEST( tpu_port ); + + /* Set IP address, pubkey, port */ + uint ip_addr; + FD_TEST( fd_cstr_to_ip4_addr( ip_token, &ip_addr ) ); + dest.ip4_addr = ip_addr; + FD_TEST( fd_base58_decode_32( pubkey_token, dest.pubkey[0].key ) ); + FD_TEST( (dest.udp_port = (ushort)atoi( tpu_port )) > 0 ); + + return dest; +} + +static inline void +send_test_ci( send_test_ctx_t * ctx, send_test_out_t * out ) { + fd_shred_dest_wire_t * dests = fd_chunk_to_laddr( out->mem, out->chunk ); + ulong dest_count = 0; + + FILE * file = fopen( ctx->gossip_file, "r" ); + if( !file ) FD_LOG_ERR(( "Failed to open gossip file: %s", ctx->gossip_file )); + + ulong const batch_cnt = USHORT_MAX/sizeof(fd_shred_dest_wire_t); + + char line[1024]; + while( fgets( line, sizeof(line), file ) ) { + dests[dest_count++] = parse_gossip_line( line ); + if( dest_count == batch_cnt ) { + ulong const sz = sizeof(fd_shred_dest_wire_t) * dest_count; + FD_TEST( sz <= USHORT_MAX ); + + /* Publish batch */ + fd_mcache_publish( out->mcache, out->depth, out->seq, 0UL, out->chunk, sz, 0UL, 0UL, 0UL ); + out->seq = fd_seq_inc( out->seq, 1UL ); + out->chunk = fd_dcache_compact_next( out->chunk, sz, out->chunk0, out->wmark ); + + dests = fd_chunk_to_laddr( out->mem, out->chunk ); /* reset dests */ + dest_count = 0; + } + } + fclose( file ); + + /* Publish batch */ + ulong const sz = sizeof(fd_shred_dest_wire_t) * dest_count; + FD_TEST( sz <= USHORT_MAX ); + fd_mcache_publish( out->mcache, out->depth, out->seq, 0UL, out->chunk, sz, 0UL, 0UL, 0UL ); + out->seq = fd_seq_inc( out->seq, 1UL ); + out->chunk = fd_dcache_compact_next( out->chunk, sz, out->chunk0, out->wmark ); +} + +static inline fd_vote_stake_weight_t +parse_stake_weight( char * line ) { + fd_vote_stake_weight_t weight; + FD_LOG_DEBUG(( "Parsing stake line: %s", line )); + + /* Set pubkeys */ + char * id_token = strtok( line+3, " \t" ); FD_TEST( id_token ); + char * vote_token = strtok( NULL, " \t" ); FD_TEST( vote_token ); + FD_TEST( fd_base58_decode_32( id_token, weight.id_key.key ) ); + FD_TEST( fd_base58_decode_32( vote_token, weight.vote_key.key ) ); + + /* Find staked amount in rest of string */ + char * sol_pos = strstr( strtok( NULL, "" ), " SOL " ); FD_TEST( sol_pos ); + char * sol_start = sol_pos - 2; + *sol_pos = '\0'; + /* Scan backwards from " SOL " to find the start of the number */ + while( sol_start > line && (sol_start[-1] == '.' || (sol_start[-1] >= '0' && sol_start[-1] <= '9')) ) { + sol_start--; + } + + /* Set staked amount */ + double sol_amount = atof( sol_start ); FD_TEST( sol_amount > 0.0 ); + weight.stake = (ulong)(sol_amount * 1000000000UL); + return weight; +} + +static inline void +send_test_stake( send_test_ctx_t * ctx, send_test_out_t * out ) { + + fd_stake_weight_msg_t * msg = fd_chunk_to_laddr( out->mem, out->chunk ); + + msg->epoch = ctx->epoch; + msg->start_slot = ctx->epoch*MAX_SLOTS_PER_EPOCH; + msg->slot_cnt = MAX_SLOTS_PER_EPOCH; + msg->excluded_stake = 0; + msg->vote_keyed_lsched = 0; + + fd_vote_stake_weight_t * stake_weights = msg->weights; + ulong stake_count = 0; + + FILE * file = fopen( ctx->stake_file, "r" ); + if( !file ) FD_LOG_ERR(( "Failed to open stake file: %s", ctx->stake_file )); + + char line[1024]; + while( fgets( line, sizeof(line), file ) ) { + stake_weights[stake_count++] = parse_stake_weight( line ); + } + fclose( file ); + + if( stake_count == 0 ) FD_LOG_ERR(( "No valid stake entries found in %s", ctx->stake_file )); + + msg->staked_cnt = stake_count; + ulong const sz = sizeof(fd_stake_weight_msg_t) + stake_count * sizeof(fd_vote_stake_weight_t); + FD_TEST( sz <= USHORT_MAX ); + + fd_mcache_publish( out->mcache, out->depth, out->seq, 0UL, out->chunk, sz, 0UL, 0UL, 0UL ); + out->seq = fd_seq_inc( out->seq, 1UL ); + out->chunk = fd_dcache_compact_next( out->chunk, sz, out->chunk0, out->wmark ); + + ctx->epoch++; +} + +static inline void +send_test_trigger( send_test_ctx_t * ctx, send_test_out_t * out ) { + uchar * buf = fd_chunk_to_laddr( out->mem, out->chunk ); + fd_memcpy( buf, ctx->txn_buf, sizeof(fd_txn_p_t) ); + + ulong const sz = sizeof(fd_txn_p_t); + ulong const sig = ctx->slot++; + fd_mcache_publish( out->mcache, out->depth, out->seq, sig, out->chunk, sz, 0UL, 0UL, 0UL ); + out->seq = fd_seq_inc( out->seq, 1UL ); + out->chunk = fd_dcache_compact_next( out->chunk, sz, out->chunk0, out->wmark ); +} + +static inline send_test_out_t +setup_test_out_link( fd_topo_t const * topo, char const * name ) { + ulong idx = fd_topo_find_link( topo, name, 0UL ); + FD_TEST( idx != ULONG_MAX ); + fd_topo_link_t const * link = &topo->links[ idx ]; + send_test_out_t out = { 0 }; + out.mcache = link->mcache; + out.sync = fd_mcache_seq_laddr( out.mcache ); + out.depth = fd_mcache_depth( out.mcache ); + out.seq = fd_mcache_seq_query( out.sync ); + out.mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp; + out.chunk0 = fd_dcache_compact_chunk0( out.mem, link->dcache ); + out.wmark = fd_dcache_compact_wmark( out.mem, link->dcache, link->mtu ); + out.chunk = out.chunk0; + return out; +} + +static inline void +encode_vote( send_test_ctx_t * ctx, fd_txn_p_t * txn ) { + ulong const root = 350284672UL; + + /* Create minimal mock tower with one vote */ + uchar tower_mem[ FD_TOWER_FOOTPRINT ] __attribute__((aligned(FD_TOWER_ALIGN))); + fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem ) ); + fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = root+1, .conf = 1 } ); + + /* Mock values */ + fd_lockout_offset_t lockouts_scratch[1]; + fd_hash_t test_hash; + + /* Use fd_tower_to_vote_txn to generate the transaction */ + fd_tower_to_vote_txn( tower, root, lockouts_scratch, &test_hash, + &test_hash, ctx->identity_key, + ctx->identity_key, ctx->vote_acct_addr, txn ); +} + +#endif /* FD_SRC_APP_FIREDANCER_DEV_COMMANDS_SEND_TEST_HELPERS_C */ diff --git a/src/app/firedancer-dev/main.c b/src/app/firedancer-dev/main.c index aeb33089964..a2602e3be57 100644 --- a/src/app/firedancer-dev/main.c +++ b/src/app/firedancer-dev/main.c @@ -183,6 +183,7 @@ extern action_t fd_action_snapshot_load; extern action_t fd_action_repair; extern action_t fd_action_shred_version; extern action_t fd_action_ipecho_server; +extern action_t fd_action_send_test; action_t * ACTIONS[] = { &fd_action_run, @@ -214,6 +215,7 @@ action_t * ACTIONS[] = { &fd_action_repair, &fd_action_shred_version, &fd_action_ipecho_server, + &fd_action_send_test, NULL, }; diff --git a/src/app/firedancer/topology.c b/src/app/firedancer/topology.c index b3a65b2d735..a2872c991a2 100644 --- a/src/app/firedancer/topology.c +++ b/src/app/firedancer/topology.c @@ -206,7 +206,7 @@ fd_topo_initialize( config_t * config ) { int enable_rpc = ( config->rpc.port != 0 ); - fd_topo_t * topo = { fd_topob_new( &config->topo, config->name ) }; + fd_topo_t * topo = fd_topob_new( &config->topo, config->name ); topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size ); topo->gigantic_page_threshold = config->hugetlbfs.gigantic_page_threshold_mib << 20; @@ -409,7 +409,7 @@ fd_topo_initialize( config_t * config ) { tile_to_cpu[ i ] = fd_ulong_if( parsed_tile_to_cpu[ i ]==USHORT_MAX, ULONG_MAX, (ulong)parsed_tile_to_cpu[ i ] ); } - fd_topos_net_tiles( topo, config->layout.net_tile_count, &config->net, config->tiles.netlink.max_routes, config->tiles.netlink.max_peer_routes, config->tiles.netlink.max_neighbors, tile_to_cpu ); + fd_topos_net_tiles( topo, net_tile_cnt, &config->net, config->tiles.netlink.max_routes, config->tiles.netlink.max_peer_routes, config->tiles.netlink.max_neighbors, tile_to_cpu ); FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_gossip", i, config->net.ingress_buffer_size ); FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_repair", i, config->net.ingress_buffer_size ); diff --git a/src/app/shared/commands/run/run.c b/src/app/shared/commands/run/run.c index 45da4152af1..7fa06633dd1 100644 --- a/src/app/shared/commands/run/run.c +++ b/src/app/shared/commands/run/run.c @@ -898,6 +898,11 @@ run_firedancer( config_t * config, void run_cmd_fn( args_t * args FD_PARAM_UNUSED, config_t * config ) { + #define CHECK_PORT_NON_ZERO( field ) \ + if( FD_UNLIKELY( config->field==0 ) ) { \ + FD_LOG_ERR(( #field " is not set in your configuration file. Please set it to a non-zero value." )); \ + } + if( FD_UNLIKELY( !config->gossip.entrypoints_cnt && !config->development.bootstrap ) ) FD_LOG_ERR(( "No entrypoints specified in configuration file under [gossip.entrypoints], but " "at least one is needed to determine how to connect to the Solana cluster. If " @@ -911,6 +916,15 @@ run_cmd_fn( args_t * args FD_PARAM_UNUSED, "empty. Please remove the empty entrypoint or set it correctly. ")); } + CHECK_PORT_NON_ZERO( gossip.port ); + CHECK_PORT_NON_ZERO( tiles.quic.quic_transaction_listen_port ); + CHECK_PORT_NON_ZERO( tiles.quic.regular_transaction_listen_port ); + CHECK_PORT_NON_ZERO( tiles.shred.shred_listen_port ); + CHECK_PORT_NON_ZERO( tiles.metric.prometheus_listen_port ); + CHECK_PORT_NON_ZERO( tiles.gui.gui_listen_port ); + + #undef CHECK_PORT_NON_ZERO + run_firedancer( config, -1, 1 ); } diff --git a/src/app/shared/fd_config.c b/src/app/shared/fd_config.c index 788ad255223..4c2b459de57 100644 --- a/src/app/shared/fd_config.c +++ b/src/app/shared/fd_config.c @@ -484,8 +484,6 @@ fd_config_validate( fd_config_t const * config ) { CFG_HAS_NON_EMPTY( log.level_stderr ); CFG_HAS_NON_EMPTY( log.level_flush ); - CFG_HAS_NON_ZERO( gossip.port ); - CFG_HAS_NON_EMPTY( layout.affinity ); CFG_HAS_NON_ZERO ( layout.net_tile_count ); CFG_HAS_NON_ZERO ( layout.quic_tile_count ); @@ -521,8 +519,6 @@ fd_config_validate( fd_config_t const * config ) { CFG_HAS_NON_ZERO( tiles.netlink.max_peer_routes ); CFG_HAS_NON_ZERO( tiles.netlink.max_neighbors ); - CFG_HAS_NON_ZERO( tiles.quic.regular_transaction_listen_port ); - CFG_HAS_NON_ZERO( tiles.quic.quic_transaction_listen_port ); CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_connections ); CFG_HAS_NON_ZERO( tiles.quic.txn_reassembly_count ); CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_handshakes ); @@ -536,16 +532,11 @@ fd_config_validate( fd_config_t const * config ) { CFG_HAS_NON_ZERO( tiles.pack.max_pending_transactions ); CFG_HAS_NON_ZERO( tiles.shred.max_pending_shred_sets ); - CFG_HAS_NON_ZERO( tiles.shred.shred_listen_port ); if( config->is_firedancer ) { CFG_HAS_POW2( tiles.repair.slot_max ); } - CFG_HAS_NON_ZERO( tiles.metric.prometheus_listen_port ); - - CFG_HAS_NON_ZERO( tiles.gui.gui_listen_port ); - if( FD_UNLIKELY( config->tiles.bundle.keepalive_interval_millis < 3000 && config->tiles.bundle.keepalive_interval_millis > 3600000 ) ) { FD_LOG_ERR(( "`tiles.bundle.keepalive_interval_millis` must be in range [3000, 3,600,000]" )); diff --git a/src/disco/net/sock/fd_sock_tile.c b/src/disco/net/sock/fd_sock_tile.c index 65cfc53b173..25b92475b21 100644 --- a/src/disco/net/sock/fd_sock_tile.c +++ b/src/disco/net/sock/fd_sock_tile.c @@ -209,18 +209,21 @@ privileged_init( fd_topo_t * topo, DST_PROTO_SHRED, /* shred_listen_port (turbine) */ DST_PROTO_GOSSIP, /* gossip_listen_port */ DST_PROTO_REPAIR, /* shred_listen_port (repair) */ - DST_PROTO_REPAIR /* repair_serve_listen_port */ + DST_PROTO_REPAIR, /* repair_serve_listen_port */ + DST_PROTO_SEND /* send_src_port */ }; - for( uint candidate_idx=0U; candidate_idx<6; candidate_idx++ ) { + for( uint candidate_idx=0U; candidate_idx<7; candidate_idx++ ) { if( !udp_port_candidates[ candidate_idx ] ) continue; uint sock_idx = ctx->sock_cnt; if( candidate_idx>FD_SOCK_TILE_MAX_SOCKETS ) FD_LOG_ERR(( "too many sockets" )); ushort port = (ushort)udp_port_candidates[ candidate_idx ]; /* Validate value of REPAIR_SHRED_SOCKET_ID */ - if( udp_port_candidates[sock_idx]==tile->sock.net.repair_intake_listen_port ) + if( tile->sock.net.repair_intake_listen_port && + udp_port_candidates[sock_idx]==tile->sock.net.repair_intake_listen_port ) FD_TEST( sock_idx==REPAIR_SHRED_SOCKET_ID ); - if( udp_port_candidates[sock_idx]==tile->sock.net.repair_serve_listen_port ) + if( tile->sock.net.repair_serve_listen_port && + udp_port_candidates[sock_idx]==tile->sock.net.repair_serve_listen_port ) FD_TEST( sock_idx==REPAIR_SHRED_SOCKET_ID+1 ); char const * target_link = udp_port_links[ candidate_idx ]; diff --git a/src/disco/stem/fd_stem.c b/src/disco/stem/fd_stem.c index f6c0f98aff7..d0318f3d529 100644 --- a/src/disco/stem/fd_stem.c +++ b/src/disco/stem/fd_stem.c @@ -102,20 +102,20 @@ not invoked if the stem is backpressured, as it would not try and read a frag from an in in the first place (instead, leaving it on the in mcache to backpressure the upstream producer). in_idx will be the - index of the in that the frag was received from. If the producer of - the frags is respecting flow control, it is safe to read frag data in - any of the callbacks, but it is suggested to copy or read frag data - within this callback, as if the producer does not respect flow - control, the frag may be torn or corrupt due to an overrun by the - reader. If the frag being read from has been overwritten while this - callback is running, the frag will be ignored and the stem will not - call the after_frag function. Instead it will recover from the - overrun and continue with new frags. This function cannot fail. The - ctx is a user-provided context object from when the stem tile was - initialized. seq, sig, chunk, and sz are the respective fields from - the mcache fragment that was received. If the producer is not - respecting flow control, these may be corrupt or torn and should not - be trusted, except for seq which is read atomically. + index of the in that the frag was received from, skipping any unpolled + links. If the producer of the frags is respecting flow control, it is + safe to read frag data in any of the callbacks, but it is suggested to + copy or read frag data within this callback, as if the producer does + not respect flow control, the frag may be torn or corrupt due to an + overrun by the reader. If the frag being read from has been + overwritten while this callback is running, the frag will be ignored + and the stem will not call the after_frag function. Instead it will + recover from the overrun and continue with new frags. This function + cannot fail. The ctx is a user-provided context object from when the + stem tile was initialized. seq, sig, chunk, and sz are the respective + fields from the mcache fragment that was received. If the producer + is not respecting flow control, these may be corrupt or torn and + should not be trusted, except for seq which is read atomically. RETURNABLE_FRAG Is called after the stem has received a new frag from an in, and @@ -148,13 +148,13 @@ the reader was overrun, the frag is abandoned and this function is not called. This callback is not invoked if the stem is backpressured, as it would not read a frag in the first place. - in_idx will be the index of the in that the frag was received from. - You should not read the frag data directly here, as it might still - get overrun, instead it should be copied out of the frag during the - read callback if needed later. This function cannot fail. The ctx is - a user-provided context object from when the stem tile was - initialized. stem should only be used for calling fd_stem_publish to - publish a fragment to downstream consumers. seq is the sequence + in_idx will be the index of the in that the frag was received from, + skipping any unpolled links. You should not read the frag data directly + here, as it might still get overrun, instead it should be copied out of + the frag during the read callback if needed later. This function cannot + fail. The ctx is a user-provided context object from when the stem tile + was initialized. stem should only be used for calling fd_stem_publish + to publish a fragment to downstream consumers. seq is the sequence number of the fragment that was read from the input mcache. sig, chunk, sz, tsorig, and tspub are the respective fields from the mcache fragment that was received. If the producer is not respecting