|
| 1 | +/* |
| 2 | +send_test is a firedancer-dev command that tests the send tile. |
| 3 | +It uses the net, send, metrics, and sign tiles, just like in prod. |
| 4 | +The main test function writes contact info to the gossip_send link, |
| 5 | +stake info to the stake_out link, and triggers mock votes on the |
| 6 | +tower_send link. |
| 7 | +
|
| 8 | +It takes two required arguments: |
| 9 | +--gossip-file: the path to the gossip file |
| 10 | +--stake-file: the path to the stake file |
| 11 | +These two files should include lines from the 'solana gossip' and |
| 12 | +'solana validators' commands, respectively. It is recommended to run |
| 13 | +with a known good subset of nodes while tuning the send tile. |
| 14 | +*/ |
| 15 | +#include "../../../shared/commands/configure/configure.h" |
| 16 | +#include "../../../shared/commands/run/run.h" /* initialize_workspaces */ |
| 17 | +#include "../../../shared/fd_config.h" /* config_t */ |
| 18 | +#include "../../../../disco/topo/fd_topob.h" |
| 19 | +#include "../../../../disco/topo/fd_cpu_topo.h" /* fd_topo_cpus_t */ |
| 20 | +#include "../../../../util/tile/fd_tile_private.h" |
| 21 | +#include "../../../../disco/net/fd_net_tile.h" /* fd_topos_net_tiles */ |
| 22 | +#include "../../../../flamenco/leaders/fd_leaders_base.h" /* FD_STAKE_OUT_MTU */ |
| 23 | +#include "../../../../disco/pack/fd_microblock.h" /* fd_txn_p_t */ |
| 24 | +#include "../../../../app/firedancer/topology.h" /* fd_topo_configure_tile */ |
| 25 | +#include "../../../../disco/keyguard/fd_keyload.h" |
| 26 | + |
| 27 | +#include "send_test_helpers.c" |
| 28 | + |
| 29 | +extern fd_topo_obj_callbacks_t * CALLBACKS[]; |
| 30 | + |
| 31 | +fd_topo_run_tile_t |
| 32 | +fdctl_tile_run( fd_topo_tile_t const * tile ); |
| 33 | + |
| 34 | +static void |
| 35 | +send_test_topo( config_t * config ) { |
| 36 | + |
| 37 | + ulong const net_tile_cnt = config->layout.net_tile_count; |
| 38 | + ulong const ingress_buf_sz = config->net.ingress_buffer_size; |
| 39 | + |
| 40 | + /* Setup topology */ |
| 41 | + fd_topo_t * topo = fd_topob_new( &config->topo, config->name ); |
| 42 | + topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size ); |
| 43 | + |
| 44 | + /* tile wksps */ |
| 45 | + fd_topob_wksp( topo, "metric_in" ); |
| 46 | + fd_topob_wksp( topo, "metric" ); |
| 47 | + fd_topob_wksp( topo, "sign" ); |
| 48 | + fd_topob_wksp( topo, "send" ); |
| 49 | + |
| 50 | + /* wksps for real links */ |
| 51 | + fd_topob_wksp( topo, "send_net" ); |
| 52 | + fd_topob_wksp( topo, "sign_send" ); |
| 53 | + fd_topob_wksp( topo, "send_sign" ); |
| 54 | + |
| 55 | + /* wksps for mock links */ |
| 56 | + fd_topob_wksp( topo, "gossip_send" ); |
| 57 | + fd_topob_wksp( topo, "stake_out" ); |
| 58 | + fd_topob_wksp( topo, "tower_send" ); |
| 59 | + fd_topob_wksp( topo, "send_txns" ); |
| 60 | + |
| 61 | + ulong tile_to_cpu[ FD_TILE_MAX ] = {0}; |
| 62 | + ushort parsed_tile_to_cpu[ FD_TILE_MAX ]; |
| 63 | + for( ulong i=0UL; i<FD_TILE_MAX; i++ ) parsed_tile_to_cpu[ i ] = USHORT_MAX; |
| 64 | + |
| 65 | + fd_topo_cpus_t cpus[1]; |
| 66 | + fd_topo_cpus_init( cpus ); |
| 67 | + |
| 68 | + ulong affinity_tile_cnt = 0UL; |
| 69 | + if( FD_LIKELY( strcmp( config->layout.affinity, "auto" ) ) ) affinity_tile_cnt = fd_tile_private_cpus_parse( config->layout.affinity, parsed_tile_to_cpu ); |
| 70 | + |
| 71 | + for( ulong i=0UL; i<affinity_tile_cnt; i++ ) { |
| 72 | + if( FD_UNLIKELY( parsed_tile_to_cpu[ i ]!=USHORT_MAX && parsed_tile_to_cpu[ i ]>=cpus->cpu_cnt ) ) |
| 73 | + FD_LOG_ERR(( "The CPU affinity string in the configuration file under [layout.affinity] specifies a CPU index of %hu, but the system " |
| 74 | + "only has %lu CPUs. You should either change the CPU allocations in the affinity string, or increase the number of CPUs " |
| 75 | + "in the system.", |
| 76 | + parsed_tile_to_cpu[ i ], cpus->cpu_cnt )); |
| 77 | + tile_to_cpu[ i ] = fd_ulong_if( parsed_tile_to_cpu[ i ]==USHORT_MAX, ULONG_MAX, (ulong)parsed_tile_to_cpu[ i ] ); |
| 78 | + } |
| 79 | + |
| 80 | + #define FOR(cnt) for( ulong i=0UL; i<cnt; i++ ) |
| 81 | + |
| 82 | + /* tiles */ |
| 83 | + 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 ); |
| 84 | + fd_topob_tile( topo, "metric", "metric", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0 ); |
| 85 | + fd_topob_tile( topo, "send", "send", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0 ); |
| 86 | + fd_topob_tile( topo, "sign", "sign", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 1 ); |
| 87 | + |
| 88 | + /* real links */ |
| 89 | + FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_send", i, ingress_buf_sz ); |
| 90 | + |
| 91 | + FOR(net_tile_cnt) fd_topob_link( topo, "send_net", "send_net", ingress_buf_sz, FD_NET_MTU, 1UL ); |
| 92 | + /**/ fd_topob_link( topo, "send_sign", "send_sign", 128UL, FD_TXN_MTU, 1UL ); |
| 93 | + /**/ fd_topob_link( topo, "sign_send", "sign_send", 128UL, 64UL, 1UL ); |
| 94 | +
|
| 95 | + /* mock links */ |
| 96 | + fd_topob_link( topo, "gossip_send", "gossip_send", 128UL, 40200UL * 38UL, 1UL ) |
| 97 | + ->permit_no_producers = 1; |
| 98 | + fd_topob_link( topo, "stake_out", "stake_out", 128UL, FD_STAKE_OUT_MTU, 1UL ) |
| 99 | + ->permit_no_producers = 1; |
| 100 | + fd_topob_link( topo, "tower_send", "tower_send", 65536UL, sizeof(fd_txn_p_t), 1UL ) |
| 101 | + ->permit_no_producers = 1; |
| 102 | + fd_topob_link( topo, "send_txns", "send_txns", 128UL, 40200UL * 38UL, 1UL ) |
| 103 | + ->permit_no_consumers = 1; |
| 104 | + |
| 105 | + /* attach mock links */ |
| 106 | + fd_topob_tile_in( topo, "send", 0UL, "metric_in", "gossip_send", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); |
| 107 | + fd_topob_tile_in( topo, "send", 0UL, "metric_in", "stake_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); |
| 108 | + fd_topob_tile_in( topo, "send", 0UL, "metric_in", "tower_send", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); |
| 109 | + |
| 110 | + /* attach real links */ |
| 111 | + fd_topos_tile_in_net( topo, "metric_in", "send_net", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); |
| 112 | + fd_topob_tile_in ( topo, "send", 0UL, "metric_in", "net_send", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); |
| 113 | + |
| 114 | + fd_topob_tile_out( topo, "send", 0UL, "send_net", 0UL ); |
| 115 | + |
| 116 | + /* unpolled links have to be last! */ |
| 117 | + fd_topob_tile_in ( topo, "sign", 0UL, "metric_in", "send_sign", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); |
| 118 | + fd_topob_tile_in ( topo, "send", 0UL, "metric_in", "sign_send", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_UNPOLLED ); |
| 119 | + fd_topob_tile_out( topo, "send", 0UL, "send_sign", 0UL ); |
| 120 | + fd_topob_tile_out( topo, "sign", 0UL, "sign_send", 0UL ); |
| 121 | + fd_topob_tile_out( topo, "send", 0UL, "send_txns", 0UL ); |
| 122 | + |
| 123 | + FOR(net_tile_cnt) fd_topos_net_tile_finish( topo, i ); |
| 124 | + |
| 125 | + for( ulong i=0UL; i<topo->tile_cnt; i++ ) { |
| 126 | + fd_topo_tile_t * tile = &topo->tiles[ i ]; |
| 127 | + if( !fd_topo_configure_tile( tile, config ) ) { |
| 128 | + FD_LOG_ERR(( "unknown tile name %lu `%s`", i, tile->name )); |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + /* Finish topology setup */ |
| 133 | + if( FD_UNLIKELY( !strcmp( config->layout.affinity, "auto" ) ) ) fd_topob_auto_layout( topo, 0 ); |
| 134 | + fd_topob_finish( topo, CALLBACKS ); |
| 135 | +} |
| 136 | + |
| 137 | +struct { |
| 138 | + char gossip_file[256]; |
| 139 | + char stake_file[256]; |
| 140 | +} send_test_args = {0}; |
| 141 | + |
| 142 | +static void |
| 143 | +send_test_cmd_args( int * pargc, |
| 144 | + char *** pargv, |
| 145 | + args_t * args FD_PARAM_UNUSED ) { |
| 146 | + char ** _pargv = *pargv; |
| 147 | + int _pargc = *pargc; |
| 148 | + int found_gossip = 0; |
| 149 | + int found_stake = 0; |
| 150 | + |
| 151 | + /* Extract our arguments */ |
| 152 | + for( int i = 0; i < _pargc - 1; i++ ) { |
| 153 | + if( !strcmp( _pargv[i], "--gossip-file" ) ) { |
| 154 | + strncpy( send_test_args.gossip_file, _pargv[i+1], sizeof(send_test_args.gossip_file) - 1 ); |
| 155 | + found_gossip = 1; |
| 156 | + } else if( !strcmp( _pargv[i], "--stake-file" ) ) { |
| 157 | + strncpy( send_test_args.stake_file, _pargv[i+1], sizeof(send_test_args.stake_file) - 1 ); |
| 158 | + found_stake = 1; |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + /* Remove our arguments from argv */ |
| 163 | + int write_idx = 0; |
| 164 | + for( int read_idx = 0; read_idx < _pargc; read_idx++ ) { |
| 165 | + if( read_idx < _pargc - 1 && |
| 166 | + (!strcmp( _pargv[read_idx], "--gossip-file" ) || !strcmp( _pargv[read_idx], "--stake-file" )) ) { |
| 167 | + read_idx++; /* Skip the argument value too */ |
| 168 | + } else { |
| 169 | + _pargv[write_idx++] = _pargv[read_idx]; |
| 170 | + } |
| 171 | + } |
| 172 | + *pargc = write_idx; |
| 173 | + |
| 174 | + if( !found_gossip ) FD_LOG_ERR(( "--gossip-file is required" )); |
| 175 | + if( !found_stake ) FD_LOG_ERR(( "--stake-file is required" )); |
| 176 | +} |
| 177 | + |
| 178 | + |
| 179 | +static void |
| 180 | +init( send_test_ctx_t * ctx, config_t * config ) { |
| 181 | + fd_topo_t * topo = &config->topo; |
| 182 | + ctx->topo = topo; |
| 183 | + ctx->config = config; |
| 184 | + |
| 185 | + /* Copy file paths from send_test_args */ |
| 186 | + fd_memcpy( ctx->gossip_file, send_test_args.gossip_file, sizeof(ctx->gossip_file) ); |
| 187 | + fd_memcpy( ctx->stake_file, send_test_args.stake_file, sizeof(ctx->stake_file ) ); |
| 188 | + |
| 189 | + ctx->identity_key [ 0 ] = *(fd_pubkey_t const *)(fd_keyload_load( config->paths.identity_key, /* pubkey only: */ 1 ) ); |
| 190 | + ctx->vote_acct_addr[ 0 ] = *(fd_pubkey_t const *)(fd_keyload_load( config->paths.vote_account, /* pubkey only: */ 1 ) ); |
| 191 | + |
| 192 | + ctx->out_links[ MOCK_CI_IDX ] = setup_test_out_link( topo, "gossip_send" ); |
| 193 | + ctx->out_links[ MOCK_STAKE_IDX ] = setup_test_out_link( topo, "stake_out" ); |
| 194 | + ctx->out_links[ MOCK_TRIGGER_IDX ] = setup_test_out_link( topo, "tower_send" ); |
| 195 | + |
| 196 | + ctx->out_fns [ MOCK_CI_IDX ] = send_test_ci; |
| 197 | + ctx->out_fns [ MOCK_STAKE_IDX ] = send_test_stake; |
| 198 | + ctx->out_fns [ MOCK_TRIGGER_IDX ] = send_test_trigger; |
| 199 | + |
| 200 | + ctx->last_evt [ MOCK_CI_IDX ] = 0; |
| 201 | + ctx->last_evt [ MOCK_STAKE_IDX ] = 0; |
| 202 | + ctx->last_evt [ MOCK_TRIGGER_IDX ] = 0; |
| 203 | + |
| 204 | + ctx->delay [ MOCK_CI_IDX ] = 5e9L; |
| 205 | + ctx->delay [ MOCK_STAKE_IDX ] = 172800e9L; |
| 206 | + ctx->delay [ MOCK_TRIGGER_IDX ] = 400e6L; |
| 207 | + |
| 208 | + encode_vote( ctx, ctx->txn_buf ); |
| 209 | + |
| 210 | + /* send first epoch of stake info */ |
| 211 | + send_test_stake( ctx, &ctx->out_links[ MOCK_STAKE_IDX ] ); |
| 212 | +} |
| 213 | +static void |
| 214 | +send_test_main_loop( send_test_ctx_t * ctx ) { |
| 215 | + for(;;) { |
| 216 | + long now = fd_tickcount(); |
| 217 | + for( ulong i=0UL; i<MOCK_CNT; i++ ) { |
| 218 | + if( ctx->last_evt[ i ] + ctx->delay[ i ] <= now ) { |
| 219 | + send_test_out_t * out = &ctx->out_links[ i ]; |
| 220 | + ctx->out_fns [ i ]( ctx, out ); |
| 221 | + ctx->last_evt[ i ] = now; |
| 222 | + } |
| 223 | + } |
| 224 | + } |
| 225 | +} |
| 226 | + |
| 227 | +static void |
| 228 | +send_test_cmd_fn( args_t * args , |
| 229 | + config_t * config ) { |
| 230 | + send_test_topo( config ); |
| 231 | + |
| 232 | + configure_stage( &fd_cfg_stage_sysctl, CONFIGURE_CMD_INIT, config ); |
| 233 | + configure_stage( &fd_cfg_stage_hugetlbfs, CONFIGURE_CMD_INIT, config ); |
| 234 | + configure_stage( &fd_cfg_stage_ethtool_channels, CONFIGURE_CMD_INIT, config ); |
| 235 | + configure_stage( &fd_cfg_stage_ethtool_gro, CONFIGURE_CMD_INIT, config ); |
| 236 | + configure_stage( &fd_cfg_stage_ethtool_loopback, CONFIGURE_CMD_INIT, config ); |
| 237 | + |
| 238 | + fd_topo_print_log( 0, &config->topo ); |
| 239 | + |
| 240 | + run_firedancer_init( config, !args->dev.no_init_workspaces ); |
| 241 | + fdctl_setup_netns( config, 1 ); |
| 242 | + |
| 243 | + if( 0==strcmp( config->net.provider, "xdp" ) ) fd_topo_install_xdp( &config->topo, config->net.bind_address_parsed ); |
| 244 | + |
| 245 | + fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_WRITE ); |
| 246 | + fd_topo_run_single_process( &config->topo, 2, config->uid, config->gid, fdctl_tile_run ); |
| 247 | + |
| 248 | + send_test_ctx_t ctx = {0}; |
| 249 | + init( &ctx, config ); |
| 250 | + send_test_main_loop( &ctx ); |
| 251 | +} |
| 252 | + |
| 253 | +static void |
| 254 | +configure_stage_perm( configure_stage_t const * stage, |
| 255 | + fd_cap_chk_t * chk, |
| 256 | + config_t const * config ) { |
| 257 | + int enabled = !stage->enabled || stage->enabled( config ); |
| 258 | + if( enabled && stage->check( config ).result != CONFIGURE_OK ) |
| 259 | + if( stage->init_perm ) stage->init_perm( chk, config ); |
| 260 | +} |
| 261 | + |
| 262 | +static void |
| 263 | +send_test_cmd_perm( args_t * args FD_PARAM_UNUSED, |
| 264 | + fd_cap_chk_t * chk, |
| 265 | + config_t const * config ) { |
| 266 | + configure_stage_perm( &fd_cfg_stage_sysctl, chk, config ); |
| 267 | + configure_stage_perm( &fd_cfg_stage_hugetlbfs, chk, config ); |
| 268 | + configure_stage_perm( &fd_cfg_stage_ethtool_channels, chk, config ); |
| 269 | + configure_stage_perm( &fd_cfg_stage_ethtool_gro, chk, config ); |
| 270 | + configure_stage_perm( &fd_cfg_stage_ethtool_loopback, chk, config ); |
| 271 | +} |
| 272 | + |
| 273 | +action_t fd_action_send_test = { |
| 274 | + .name = "send_test", |
| 275 | + .args = send_test_cmd_args, |
| 276 | + .fn = send_test_cmd_fn, |
| 277 | + .perm = send_test_cmd_perm, |
| 278 | +}; |
0 commit comments