From 4aa0987c0aaed51fc91642ae3e797d519a54b6fe Mon Sep 17 00:00:00 2001 From: Tom Pointon Date: Mon, 19 Oct 2026 02:50:50 +0000 Subject: [PATCH] disco, flamenco: SIMD-0675 geo-aware leader schedule --- src/app/firedancer-dev/commands/repair.c | 750 +----------------- src/app/shared/fd_action.h | 1 - src/disco/gui/fd_gui.c | 3 +- src/disco/shred/fd_stake_ci.c | 6 +- src/disco/shred/fd_stake_ci.h | 1 + src/disco/shred/test_shred_dest.c | 18 +- src/disco/shred/test_stake_ci.c | 47 ++ src/discof/backup/fd_ssmanifest_encoder.c | 108 ++- src/discof/backup/test_snap_roundtrip.c | 33 +- src/discof/execrp/test_execrp_tile.c | 4 +- src/discof/replay/test_replay_tile.c | 11 +- src/discof/restore/utils/fd_ssload.c | 6 +- .../restore/utils/fd_ssmanifest_parser.c | 11 +- src/discof/restore/utils/fd_ssmsg.h | 4 + src/discof/restore/utils/test_ssload.c | 18 + src/discof/rpc/fuzz_rpc.c | 1 + src/discof/rpc/test_rpc_tile.c | 1 + src/discof/tower/test_tower_tile.c | 4 +- src/discof/votor/test_votor_tile.c | 1 + src/flamenco/features/fd_features_generated.c | 9 + src/flamenco/features/fd_features_generated.h | 5 +- src/flamenco/features/feature_map.json | 3 +- src/flamenco/leaders/Local.mk | 6 + src/flamenco/leaders/fd_leaders.c | 266 ++++++- src/flamenco/leaders/fd_leaders.h | 26 +- src/flamenco/leaders/fd_multi_epoch_leaders.c | 7 +- src/flamenco/leaders/fd_multi_epoch_leaders.h | 1 + src/flamenco/leaders/fuzz_leaders_geo.c | 58 ++ src/flamenco/leaders/test_leaders.c | 4 +- src/flamenco/leaders/test_leaders_geo.c | 377 +++++++++ src/flamenco/leaders/test_leaders_geo_sim.c | 127 +++ src/flamenco/runtime/fd_runtime.c | 8 +- .../program/vote/fd_validator_location.h | 9 + .../runtime/program/vote/fd_vote_codec.c | 7 + .../runtime/program/vote/fd_vote_codec.h | 4 + .../program/vote/test_validator_location.c | 17 + .../runtime/program/vote/test_vote_codec.c | 7 + .../runtime/test_vat_refresh_vote_accounts.c | 115 ++- src/flamenco/runtime/tests/fd_block_harness.c | 5 +- src/flamenco/runtime/tests/fd_svm_mini.c | 7 +- src/flamenco/runtime/tests/test_fee_reward.c | 8 +- .../runtime/tests/test_inflation_rewards.c | 28 +- src/flamenco/stakes/fd_stake_weight.h | 10 +- src/flamenco/stakes/fd_stakes.c | 4 +- src/flamenco/stakes/fd_vote_stakes.c | 182 +++-- src/flamenco/stakes/fd_vote_stakes.h | 123 +-- src/flamenco/stakes/test_vote_stakes.c | 129 ++- 47 files changed, 1547 insertions(+), 1033 deletions(-) create mode 100644 src/flamenco/leaders/fuzz_leaders_geo.c create mode 100644 src/flamenco/leaders/test_leaders_geo.c create mode 100644 src/flamenco/leaders/test_leaders_geo_sim.c diff --git a/src/app/firedancer-dev/commands/repair.c b/src/app/firedancer-dev/commands/repair.c index c2d0f48d642..72aa2375496 100644 --- a/src/app/firedancer-dev/commands/repair.c +++ b/src/app/firedancer-dev/commands/repair.c @@ -1,36 +1,20 @@ -/* The repair command spawns a smaller topology for profiling the repair - tile. This is a standalone application, and it can be run in mainnet, - testnet and/or a private cluster. */ +/* The repair command inspects the repair tile of a running validator. */ #include "../../../discof/repair/fd_repair_tile.h" #include "../../../disco/shred/fd_shred_tile.h" #include "../../../disco/net/fd_net_tile.h" #include "../../../disco/tiles.h" #include "../../../disco/topo/fd_topob.h" -#include "../../../disco/topo/fd_cpu_topo.h" #include "../../../util/pod/fd_pod_format.h" -#include "../../firedancer/topology.h" -#include "../../shared/commands/configure/configure.h" -#include "../../shared/commands/run/run.h" /* initialize_workspaces */ #include "../../shared/fd_config.h" /* config_t */ #include "../../shared_dev/commands/dev.h" #include "../../../disco/topo/fd_topob.h" #include "../../../util/pod/fd_pod_format.h" #include "../../../waltz/resolv/fd_io_readline.h" -#include "../../platform/fd_sys_util.h" #include "../../shared/commands/monitor/helper.h" #include "../../../disco/metrics/fd_metrics.h" -#include "../../../discof/restore/utils/fd_ssmanifest_parser.h" -#include "../../../discof/genesis/fd_genesi_tile.h" -#include "../../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h" -#include "../../../flamenco/stakes/fd_stake_weight_sort.h" -#include "../../../flamenco/leaders/fd_leaders_base.h" #include "../../../discof/repair/fd_repair_tile_private.h" -#include "../../../discof/tower/fd_tower_tile.h" - -#include "gossip.h" -#include "core_subtopo.h" #include /* pause */ #include @@ -63,432 +47,6 @@ restore_terminal( void ) { (void)tcsetattr( STDIN_FILENO, TCSANOW, &termios_backup ); } -extern fd_topo_obj_callbacks_t * CALLBACKS[]; - -fd_topo_run_tile_t -fdctl_tile_run( fd_topo_tile_t const * tile ); - -#define MANIFEST_LOAD_MAX_SZ (2UL * FD_SHMEM_GIGANTIC_PAGE_SZ) - -/* https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L632 */ -static int -repair_verify_epoch_stakes( fd_snapshot_manifest_t const * manifest ) { - fd_epoch_schedule_t epoch_schedule = (fd_epoch_schedule_t){ - .slots_per_epoch = manifest->epoch_schedule_params.slots_per_epoch, - .leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset, - .warmup = manifest->epoch_schedule_params.warmup, - .first_normal_epoch = manifest->epoch_schedule_params.first_normal_epoch, - .first_normal_slot = manifest->epoch_schedule_params.first_normal_slot, - }; - - ulong min_required_epoch = fd_slot_to_epoch( &epoch_schedule, manifest->slot, NULL ); - ulong max_required_epoch = fd_slot_to_leader_schedule_epoch( &epoch_schedule, manifest->slot ); - - for( ulong i=min_required_epoch; i<=max_required_epoch; i++ ) { - int found = 0; - for( ulong j=0UL; jepoch_stakes[j].epoch==i ) { - found = 1; - break; - } - } - if( FD_UNLIKELY( !found ) ) { - FD_LOG_WARNING(( "stakes not found for epoch %lu in manifest", i )); - return -1; - } - } - return 0; -} - -static inline ulong -repair_generate_epoch_info_msg( ulong epoch, - fd_epoch_schedule_t const * epoch_schedule, - fd_snapshot_manifest_epoch_stakes_t const * epoch_stakes, - ulong * epoch_info_msg_out ) { - fd_epoch_info_msg_t * epoch_info_msg = (fd_epoch_info_msg_t *)fd_type_pun( epoch_info_msg_out ); - fd_vote_stake_weight_t * stake_weights = fd_epoch_info_msg_stake_weights( epoch_info_msg ); - - epoch_info_msg->epoch = epoch; - epoch_info_msg->start_slot = fd_epoch_slot0( epoch_schedule, epoch ); - epoch_info_msg->slot_cnt = fd_epoch_slot_cnt( epoch_schedule, epoch ); - epoch_info_msg->ns_per_slot = FD_SLOT_PARAMS_400MS.ns_per_slot; - - fd_memset( &epoch_info_msg->features, 0xFF, sizeof(fd_features_t) ); - - ulong idx = 0UL; - for( ulong i=0UL; ivote_stakes_len; i++ ) { - ulong stake = epoch_stakes->vote_stakes[ i ].stake; - if( FD_UNLIKELY( !stake ) ) continue; - stake_weights[ idx ].stake = stake; - memcpy( stake_weights[ idx ].id_key.uc, epoch_stakes->vote_stakes[ i ].identity, sizeof(fd_pubkey_t) ); - memcpy( stake_weights[ idx ].vote_key.uc, epoch_stakes->vote_stakes[ i ].vote, sizeof(fd_pubkey_t) ); - idx++; - } - epoch_info_msg->staked_vote_cnt = idx; - sort_vote_weights_by_stake_vote_inplace( stake_weights, idx ); - - fd_stake_weight_t * id_weights = fd_epoch_info_msg_id_weights( epoch_info_msg ); - epoch_info_msg->staked_id_cnt = compute_id_weights_from_vote_weights( id_weights, stake_weights, epoch_info_msg->staked_vote_cnt ); - FD_TEST( idx<=MAX_SHRED_DESTS ); - - epoch_info_msg->epoch_schedule = *epoch_schedule; - return fd_epoch_info_msg_sz( epoch_info_msg->staked_vote_cnt, epoch_info_msg->staked_id_cnt ); -} - -/* repair_load_manifest loads the snapshot manifest from disk and - pre-populates the snapin_manif and replay_epoch dcache links so - that consumer tiles see the data on their first poll cycle. */ -static void -repair_load_manifest( fd_topo_t * topo, - char const * manifest_path ) { - if( FD_UNLIKELY( !manifest_path || !manifest_path[0] ) ) return; - - /* Parse manifest */ - - int fd = open( manifest_path, O_RDONLY ); - if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "open(%s) failed (%d-%s)", manifest_path, errno, fd_io_strerror( errno ) )); - - fd_snapshot_manifest_t * manifest = aligned_alloc( alignof(fd_snapshot_manifest_t), sizeof(fd_snapshot_manifest_t) ); - FD_TEST( manifest ); - for( ulong i=0UL; iepoch_stakes[i].epoch = ULONG_MAX; - - uchar * buf = aligned_alloc( 128UL, MANIFEST_LOAD_MAX_SZ ); - FD_TEST( buf ); - ulong buf_sz = 0; - FD_TEST( !fd_io_read( fd, buf, 0UL, MANIFEST_LOAD_MAX_SZ-1UL, &buf_sz ) ); - close( fd ); - - fd_ssmanifest_parser_t * parser = fd_ssmanifest_parser_join( fd_ssmanifest_parser_new( - aligned_alloc( fd_ssmanifest_parser_align(), fd_ssmanifest_parser_footprint() ) ) ); - FD_TEST( parser ); - fd_ssmanifest_parser_init( parser, manifest ); - int parser_err = fd_ssmanifest_parser_consume( parser, buf, buf_sz ); - FD_TEST( parser_err!=FD_SSMANIFEST_PARSER_ADVANCE_ERROR ); - FD_TEST( fd_ssmanifest_parser_fini( parser )==FD_SSMANIFEST_PARSER_ADVANCE_DONE ); - free( parser ); - free( buf ); - - FD_LOG_NOTICE(( "manifest bank slot %lu", manifest->slot )); - FD_TEST( !repair_verify_epoch_stakes( manifest ) ); - - /* Update root_slot fseq */ - - ulong root_slot_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "root_slot" ); - if( FD_LIKELY( root_slot_obj_id!=ULONG_MAX ) ) { - ulong * root_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, root_slot_obj_id ) ); - FD_TEST( root_fseq ); - fd_fseq_update( root_fseq, manifest->slot ); - } - - /* Publish manifest to snapin_manif dcache */ - - ulong snap_link_idx = fd_topo_find_link( topo, "snapin_manif", 0UL ); - FD_TEST( snap_link_idx!=ULONG_MAX ); - fd_topo_link_t * snap_link = &topo->links[ snap_link_idx ]; - fd_wksp_t * snap_mem = topo->workspaces[ topo->objs[ snap_link->dcache_obj_id ].wksp_id ].wksp; - ulong snap_chunk0 = fd_dcache_compact_chunk0( snap_mem, snap_link->dcache ); - ulong snap_wmark = fd_dcache_compact_wmark ( snap_mem, snap_link->dcache, snap_link->mtu ); - ulong snap_chunk = snap_chunk0; - - uchar * snap_dst = fd_chunk_to_laddr( snap_mem, snap_chunk ); - memcpy( snap_dst, manifest, sizeof(fd_snapshot_manifest_t) ); - fd_mcache_publish( snap_link->mcache, snap_link->depth, 0UL, - fd_ssmsg_sig( FD_SSMSG_MANIFEST_INCREMENTAL ), - snap_chunk, sizeof(fd_snapshot_manifest_t), 0UL, 0UL, 0UL ); - snap_chunk = fd_dcache_compact_next( snap_chunk, sizeof(fd_snapshot_manifest_t), snap_chunk0, snap_wmark ); - - fd_mcache_publish( snap_link->mcache, snap_link->depth, 1UL, - fd_ssmsg_sig( FD_SSMSG_DONE ), 0UL, 0UL, 0UL, 0UL, 0UL ); - - /* Publish epoch stake weights to replay_epoch dcache */ - - ulong epoch_link_idx = fd_topo_find_link( topo, "replay_epoch", 0UL ); - FD_TEST( epoch_link_idx!=ULONG_MAX ); - fd_topo_link_t * epoch_link = &topo->links[ epoch_link_idx ]; - fd_wksp_t * epoch_mem = topo->workspaces[ topo->objs[ epoch_link->dcache_obj_id ].wksp_id ].wksp; - ulong epoch_chunk0 = fd_dcache_compact_chunk0( epoch_mem, epoch_link->dcache ); - ulong epoch_wmark = fd_dcache_compact_wmark ( epoch_mem, epoch_link->dcache, epoch_link->mtu ); - ulong epoch_chunk = epoch_chunk0; - ulong epoch_seq = 0UL; - - /* Construct fd_epoch_schedule_t field-by-field rather than type-punning - from the unpacked manifest struct (fd_epoch_schedule_t is packed). */ - fd_epoch_schedule_t schedule_local; - schedule_local.slots_per_epoch = manifest->epoch_schedule_params.slots_per_epoch; - schedule_local.leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset; - schedule_local.warmup = manifest->epoch_schedule_params.warmup; - schedule_local.first_normal_epoch = manifest->epoch_schedule_params.first_normal_epoch; - schedule_local.first_normal_slot = manifest->epoch_schedule_params.first_normal_slot; - fd_epoch_schedule_t const * schedule = &schedule_local; - ulong epoch = fd_slot_to_epoch( schedule, manifest->slot, NULL ); - - ulong epoch_stakes_base = epoch > 3UL ? epoch - 3UL : 0UL; - ulong leader_schedule_epoch = fd_slot_to_leader_schedule_epoch( schedule, manifest->slot ); - ulong cur_idx = epoch - epoch_stakes_base; - FD_TEST( cur_idx < FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN ); - - ulong * epoch_dst = fd_chunk_to_laddr( epoch_mem, epoch_chunk ); - ulong epoch_sz = repair_generate_epoch_info_msg( epoch, schedule, &manifest->epoch_stakes[cur_idx], epoch_dst ); - fd_mcache_publish( epoch_link->mcache, epoch_link->depth, epoch_seq, - 4UL, epoch_chunk, epoch_sz, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) ); - epoch_chunk = fd_dcache_compact_next( epoch_chunk, epoch_sz, epoch_chunk0, epoch_wmark ); - epoch_seq++; - FD_LOG_NOTICE(( "sending current epoch stake weights - epoch: %lu", epoch )); - - if( leader_schedule_epoch >= epoch + 1UL ) { - ulong next_idx = epoch + 1UL - epoch_stakes_base; - FD_TEST( next_idx < FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN ); - - epoch_dst = fd_chunk_to_laddr( epoch_mem, epoch_chunk ); - epoch_sz = repair_generate_epoch_info_msg( epoch + 1UL, schedule, &manifest->epoch_stakes[next_idx], epoch_dst ); - fd_mcache_publish( epoch_link->mcache, epoch_link->depth, epoch_seq, - 4UL, epoch_chunk, epoch_sz, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) ); - epoch_chunk = fd_dcache_compact_next( epoch_chunk, epoch_sz, epoch_chunk0, epoch_wmark ); - epoch_seq++; - FD_LOG_NOTICE(( "sending next epoch stake weights - epoch: %lu", epoch + 1UL )); - } - (void)epoch_chunk; - - free( manifest ); -} - -/* repair_topo is a subset of "src/app/firedancer/topology.c" at commit - 0d8386f4f305bb15329813cfe4a40c3594249e96, slightly modified to work - as a repair catchup. TODO ideally, one should invoke the firedancer - topology first, and exclude the parts that are not needed, instead of - manually generating new topologies for every command. This would - also guarantee that the catchup is replicating (as close as possible) - the full topology. */ -static void -repair_topo( config_t * config ) { - ulong net_tile_cnt = config->layout.net_tile_count; - ulong shred_tile_cnt = config->layout.shred_tile_count; - ulong quic_tile_cnt = config->layout.quic_tile_count; - ulong sign_tile_cnt = config->firedancer.layout.sign_tile_count; - ulong gossvf_tile_cnt = config->firedancer.layout.gossvf_tile_count; - - 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; - - ulong tile_to_cpu[ FD_TILE_MAX ] = {0}; - ushort parsed_tile_to_cpu[ FD_TILE_MAX ]; - /* Unassigned tiles will be floating, unless auto topology is enabled. */ - for( ulong i=0UL; ilayout.affinity, "auto" ); - int is_bench_auto_affinity = !strcmp( config->development.bench.affinity, "auto" ); - - if( FD_UNLIKELY( is_auto_affinity != is_bench_auto_affinity ) ) { - FD_LOG_ERR(( "The CPU affinity string in the configuration file under [layout.affinity] and [development.bench.affinity] must all be set to 'auto' or all be set to a specific CPU affinity string." )); - } - - fd_topo_cpus_t cpus[1]; - fd_topo_cpus_init( cpus ); - - ulong affinity_tile_cnt = 0UL; - if( FD_LIKELY( !is_auto_affinity ) ) affinity_tile_cnt = fd_topob_parse_affinity_cstr( config->layout.affinity, parsed_tile_to_cpu, 0, 1 ); - - 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.", - cpu_idx, 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 ] ); - } - - fd_core_subtopo( config, tile_to_cpu ); - fd_gossip_subtopo( config, tile_to_cpu ); - - /* topo, name */ - fd_topob_wksp( topo, "net_shred" ); - fd_topob_wksp( topo, "net_repair" ); - fd_topob_wksp( topo, "net_quic" ); - - fd_topob_wksp( topo, "shred_out" ); - fd_topob_wksp( topo, "replay_epoch" ); - - fd_topob_wksp( topo, "poh_shred" ); - - fd_topob_wksp( topo, "shred_sign" ); - fd_topob_wksp( topo, "sign_shred" ); - - fd_topob_wksp( topo, "repair_sign" ); - fd_topob_wksp( topo, "sign_repair" ); - fd_topob_wksp( topo, "rnonce" ); - fd_topob_wksp( topo, "repair_out" ); - - fd_topob_wksp( topo, "txsend_out" ); - - fd_topob_wksp( topo, "shred" ); - fd_topob_wksp( topo, "repair" ); - fd_topob_wksp( topo, "fec_sets" ); - fd_topob_wksp( topo, "snapin_manif" ); - - fd_topob_wksp( topo, "genesi_out" ); /* mock genesi_out for ipecho */ - - fd_topob_wksp( topo, "tower_out" ); /* mock tower_out for confirmation msgs. Not needed for any topo except eqvoc. */ - - #define FOR(cnt) for( ulong i=0UL; itiles.shred.max_pending_shred_sets * FD_REEDSOL_DATA_SHREDS_MAX, - FD_SHRED_STEM_BURST ) ), - USHORT_MAX + 1UL /* dcache max */ ); - - /* topo, link_name, wksp_name, depth, mtu, burst */ - FOR(quic_tile_cnt) fd_topob_link( topo, "quic_net", "net_quic", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); - FOR(shred_tile_cnt) fd_topob_link( topo, "shred_net", "net_shred", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); - - /**/ fd_topob_link( topo, "replay_epoch", "replay_epoch", 16UL, FD_EPOCH_OUT_MTU, 1UL ); - - FOR(shred_tile_cnt) fd_topob_link( topo, "shred_sign", "shred_sign", 128UL, 32UL, 1UL ); - FOR(shred_tile_cnt) fd_topob_link( topo, "sign_shred", "sign_shred", 128UL, 64UL, 1UL ); - - /**/ fd_topob_link( topo, "repair_net", "net_repair", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); - - FOR(shred_tile_cnt) fd_topob_link( topo, "shred_out", "shred_out", pending_fec_shreds_depth, sizeof(fd_shred_message_t), FD_SHRED_STEM_BURST ); - FOR(sign_tile_cnt-1) fd_topob_link( topo, "repair_sign", "repair_sign", 256UL, FD_REPAIR_MAX_PREIMAGE_SZ, 1UL ); - FOR(sign_tile_cnt-1) fd_topob_link( topo, "sign_repair", "sign_repair", 128UL, sizeof(fd_ed25519_sig_t), 1UL ); - - /**/ fd_topob_link( topo, "repair_out", "repair_out", 128UL, sizeof(fd_repair_fec_complete_t), 1UL ); - - /**/ fd_topob_link( topo, "poh_shred", "poh_shred", 16384UL, FD_POH_SHRED_MTU, 1UL ); - - /**/ fd_topob_link( topo, "txsend_out", "txsend_out", 128UL, FD_TXN_MTU, 1UL ); - - /**/ fd_topob_link( topo, "snapin_manif", "snapin_manif", 2UL, sizeof(fd_snapshot_manifest_t),1UL ); - - /**/ fd_topob_link( topo, "genesi_out", "genesi_out", 1UL, fd_genesi_tile_mtu( config->firedancer.development.genesis.max_file_size_mib<<20 ), 1UL ); - /**/ fd_topob_link( topo, "tower_out", "tower_out", 1024UL, sizeof(fd_tower_msg_t), 1UL ); - - FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_repair", i, config->net.ingress_buffer_size ); - FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_quic", i, config->net.ingress_buffer_size ); - FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_shred", i, config->net.ingress_buffer_size ); - - /* topo, tile_name, tile_wksp, metrics_wksp, cpu_idx, uses_id_keyswitch, uses_av_keyswitch */ - FOR(shred_tile_cnt) fd_topob_tile( topo, "shred", "shred", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); - /**/ fd_topob_tile( topo, "repair", "repair", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); - - /* Setup a shared wksp object for fec sets. */ - - ulong fec_set_cnt = fd_shred_tile_fec_set_cnt( FD_SHRED_FIREDANCER_FEC_EXPOSURE, - config->tiles.shred.max_pending_shred_sets ); - ulong fec_sets_sz = fec_set_cnt*sizeof(fd_fec_set_t); - fd_topo_obj_t * fec_sets_obj = setup_topo_fec_sets( topo, "fec_sets", shred_tile_cnt*fec_sets_sz ); - for( ulong i=0UL; itiles[ fd_topo_find_tile( topo, "shred", i ) ]; - fd_topob_tile_uses( topo, shred_tile, fec_sets_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - } - FD_TEST( fd_pod_insertf_ulong( topo->props, fec_sets_obj->id, "fec_sets" ) ); - - if( FD_LIKELY( !is_auto_affinity ) ) { - if( FD_UNLIKELY( affinity_tile_cnttile_cnt ) ) - FD_LOG_ERR(( "The topology you are using has %lu tiles, but the CPU affinity specified in the config tile as [layout.affinity] only provides for %lu cores. " - "You should either increase the number of cores dedicated to Firedancer in the affinity string, or decrease the number of cores needed by reducing " - "the total tile count. You can reduce the tile count by decreasing individual tile counts in the [layout] section of the configuration file.", - topo->tile_cnt, affinity_tile_cnt )); - if( FD_UNLIKELY( affinity_tile_cnt>topo->tile_cnt ) ) - FD_LOG_WARNING(( "The topology you are using has %lu tiles, but the CPU affinity specified in the config tile as [layout.affinity] provides for %lu cores. " - "Not all cores in the affinity will be used by Firedancer. You may wish to increase the number of tiles in the system by increasing " - "individual tile counts in the [layout] section of the configuration file.", - topo->tile_cnt, affinity_tile_cnt )); - } - - /* topo, tile_name, tile_kind_id, fseq_wksp, link_name, link_kind_id, reliable, polled */ - for( ulong j=0UL; jtiles[ fd_topo_find_tile( topo, "repair", 0UL ) ]; - fd_topob_tile_uses( topo, repair_tile, rnonce_ss_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); - for( ulong i=0UL; itiles[ fd_topo_find_tile( topo, "shred", i ) ]; - fd_topob_tile_uses( topo, shred_tile, rnonce_ss_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); - } - FD_TEST( fd_pod_insertf_ulong( topo->props, rnonce_ss_obj->id, "rnonce_ss" ) ); - } - - FD_TEST( fd_link_permit_no_producers( topo, "quic_net" ) == quic_tile_cnt ); - FD_TEST( fd_link_permit_no_producers( topo, "poh_shred" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "txsend_out" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "genesi_out" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "tower_out" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "replay_epoch" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "snapin_manif" ) == 1UL ); - FD_TEST( fd_link_permit_no_consumers( topo, "net_quic" ) == net_tile_cnt ); - FD_TEST( fd_link_permit_no_consumers( topo, "repair_out" ) == 1UL ); - - config->tiles.txsend.txsend_src_port = 0; /* disable txsend */ - - 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 ]; - fd_topo_configure_tile( tile, config ); - } - - if( FD_UNLIKELY( is_auto_affinity ) ) fd_topob_auto_layout( topo ); - - fd_topob_waker( topo ); - fd_topob_finish( topo, CALLBACKS ); - - config->topo = *topo; -} - static char * fmt_count( char buf[ static 64 ], ulong count ) { char tmp[ 64 ]; @@ -599,86 +157,6 @@ print_catchup_slots( fd_wksp_t * repair_tile_wksp, ctx_t * repair_ctx, int verbo } static fd_location_info_t * location_table; -static fd_pubkey_t peers_copy[ FD_REPAIR_PEER_MAX]; - -static ulong -sort_peers_by_latency( fd_policy_peer_map_t * active_table, fd_policy_peer_dlist_t * peers_dlist, fd_policy_peer_dlist_t * peers_wlist, fd_policy_peer_t * peers_arr ) { - ulong i = 0; - fd_policy_peer_dlist_iter_t iter = fd_policy_peer_dlist_iter_fwd_init( peers_dlist, peers_arr ); - while( !fd_policy_peer_dlist_iter_done( iter, peers_dlist, peers_arr ) ) { - fd_policy_peer_t * peer = fd_policy_peer_dlist_iter_ele( iter, peers_dlist, peers_arr ); - if( FD_UNLIKELY( !peer ) ) break; - peers_copy[ i++ ] = peer->key; - if( FD_UNLIKELY( i >= FD_REPAIR_PEER_MAX ) ) break; - iter = fd_policy_peer_dlist_iter_fwd_next( iter, peers_dlist, peers_arr ); - } - ulong fast_cnt = i; - iter = fd_policy_peer_dlist_iter_fwd_init( peers_wlist, peers_arr ); - while( !fd_policy_peer_dlist_iter_done( iter, peers_wlist, peers_arr ) ) { - fd_policy_peer_t * peer = fd_policy_peer_dlist_iter_ele( iter, peers_wlist, peers_arr ); - if( FD_UNLIKELY( !peer ) ) break; - peers_copy[ i++ ] = peer->key; - if( FD_UNLIKELY( i >= FD_REPAIR_PEER_MAX ) ) break; - iter = fd_policy_peer_dlist_iter_fwd_next( iter, peers_wlist, peers_arr ); - } - FD_LOG_NOTICE(( "Fast peers cnt: %lu. Slow peers cnt: %lu.", fast_cnt, i - fast_cnt )); - - ulong peer_cnt = i; - for( uint i = 0; i < peer_cnt - 1; i++ ) { - int swapped = 0; - for( uint j = 0; j < peer_cnt - 1 - i; j++ ) { - fd_policy_peer_t const * active_j = fd_policy_peer_map_ele_query( active_table, &peers_copy[ j ], NULL, peers_arr ); - fd_policy_peer_t const * active_j1 = fd_policy_peer_map_ele_query( active_table, &peers_copy[ j + 1 ], NULL, peers_arr ); - - /* Skip peers with no responses */ - double latency_j = 10e9; - double latency_j1 = 10e9; - if( FD_LIKELY( active_j && active_j->res_cnt > 0 ) ) latency_j = ((double)active_j->total_lat / (double)active_j->res_cnt); - if( FD_LIKELY( active_j1 && active_j1->res_cnt > 0 ) ) latency_j1 = ((double)active_j1->total_lat / (double)active_j1->res_cnt); - - /* Swap if j has higher latency than j+1 */ - if( latency_j > latency_j1 ) { - fd_pubkey_t temp = peers_copy[ j ]; - peers_copy[ j ] = peers_copy[ j + 1 ]; - peers_copy[ j + 1 ] = temp; - swapped = 1; - } - } - if( !swapped ) break; - } - return peer_cnt; -} - -static void -print_peer_location_latency( fd_wksp_t * repair_tile_wksp, ctx_t * tile_ctx ) { - ulong policy_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, tile_ctx->policy ); - fd_policy_t * policy = fd_wksp_laddr ( repair_tile_wksp, policy_gaddr ); - ulong peermap_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, policy->peers.map ); - ulong peerarr_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, policy->peers.pool ); - ulong peerlst_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, policy->peers.fast ); - ulong peerwst_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, policy->peers.slow ); - fd_policy_peer_map_t * peers_map = (fd_policy_peer_map_t *) fd_wksp_laddr( repair_tile_wksp, peermap_gaddr ); - fd_policy_peer_dlist_t * peers_dlist = (fd_policy_peer_dlist_t *)fd_wksp_laddr( repair_tile_wksp, peerlst_gaddr ); - fd_policy_peer_dlist_t * peers_wlist = (fd_policy_peer_dlist_t *)fd_wksp_laddr( repair_tile_wksp, peerwst_gaddr ); - fd_policy_peer_t * peers_arr = (fd_policy_peer_t *) fd_wksp_laddr( repair_tile_wksp, peerarr_gaddr ); - - ulong peer_cnt = sort_peers_by_latency( peers_map, peers_dlist, peers_wlist, peers_arr ); - printf("\nPeer Location/Latency Information\n"); - printf( " | %-46s | %-7s | %-8s | %-8s | %-7s | %-7s | %-12s | %s\n", "Pubkey", "Req Cnt", "Req B/s", "Rx B/s", "Rx Rate", "Avg Latency", "Ewma Latency", "Location Info" ); - for( uint i = 0; i < peer_cnt; i++ ) { - fd_policy_peer_t const * active = fd_policy_peer_map_ele_query( peers_map, &peers_copy[ i ], NULL, peers_arr ); - if( FD_LIKELY( active && active->res_cnt > 0 ) ) { - fd_location_info_t * info = fd_location_table_query( location_table, active->ip4, NULL ); - char * geolocation = info ? info->location : ""; - double peer_bps = (double)(active->res_cnt * FD_SHRED_MIN_SZ) / ((double)(active->last_resp_ts - active->first_resp_ts) / 1e9); - double req_bps = (double)active->req_cnt * 202 / ((double)(active->last_req_ts - active->first_req_ts) / 1e9); - FD_BASE58_ENCODE_32_BYTES( active->key.key, key_b58 ); - printf( "%-5u | %-46s | %-7lu | %-8.2f | %-8.2f | %-7.2f | %10.3fms | %10.3fms | %s\n", i, key_b58, active->req_cnt, req_bps, peer_bps, (double)active->res_cnt / (double)active->req_cnt, ((double)active->total_lat / (double)active->res_cnt) / 1e6, (double)active->ewma_lat / 1e6, geolocation ); - } - } - printf("\n"); - fflush( stdout ); -} static void read_iptable( char * iptable_path, fd_location_info_t * location_table ) { @@ -824,202 +302,6 @@ repair_ctx_wksp( args_t * args, *repair_wksp = _repair_wksp; } -static void -repair_cmd_fn_catchup( args_t * args, - config_t * config ) { - - memset( &config->topo, 0, sizeof(config->topo) ); - repair_topo( config ); - - fd_topo_print_log( 1, &config->topo ); - - args_t configure_args = { - .configure.command = CONFIGURE_CMD_INIT, - }; - for( ulong i=0UL; STAGES[ i ]; i++ ) { - configure_args.configure.stages[ i ] = STAGES[ i ]; - } - configure_cmd_fn( &configure_args, config ); - if( 0==strcmp( config->net.provider, "xdp" ) ) { - fd_topo_install_xdp_simple( &config->topo, config->net.bind_address_parsed ); - } - run_firedancer_init( config, 1, 0 ); - - fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - if( 0==strcmp( config->net.provider, "mlx5" ) ) { - fd_topo_install_mlx5( &config->topo, NULL ); - } - - fd_topo_fill( &config->topo ); - - repair_load_manifest( &config->topo, args->repair.manifest_path ); - - /* Access repair workspace memory and metrics */ - - ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "repair", 0UL ); - ulong shred_tile_idx = fd_topo_find_tile( &config->topo, "shred", 0UL ); - FD_TEST( repair_tile_idx!=ULONG_MAX ); - FD_TEST( shred_tile_idx !=ULONG_MAX ); - fd_topo_tile_t * repair_tile = &config->topo.tiles[ repair_tile_idx ]; - fd_topo_tile_t * shred_tile = &config->topo.tiles[ shred_tile_idx ]; - - fd_topo_wksp_t * repair_wksp; - ctx_t * repair_ctx; - repair_ctx_wksp( args, config, &repair_ctx, &repair_wksp ); - - volatile ulong * shred_metrics = fd_metrics_tile( shred_tile->metrics ); - volatile ulong * repair_metrics = fd_metrics_tile( repair_tile->metrics ); - FD_TEST( repair_metrics ); - ulong * repair_metrics_prev = aligned_alloc( 8UL, sizeof(ulong) * FD_METRICS_TOTAL_SZ ); - FD_TEST( repair_metrics_prev ); - memset( repair_metrics_prev, 0, sizeof(ulong) * FD_METRICS_TOTAL_SZ ); - - /* Collect link metrics */ - - /* Collect all net tiles and their repair_net link metrics */ - ulong net_cnt = config->layout.net_tile_count; - volatile ulong ** repair_net_links = aligned_alloc( 8UL, net_cnt * sizeof(volatile ulong*) ); - volatile ulong ** net_shred_links = aligned_alloc( 8UL, net_cnt * sizeof(volatile ulong*) ); - FD_TEST( repair_net_links ); - FD_TEST( net_shred_links ); - - char const * net_tile_name = fd_net_tile_name( config->net.provider ); - for( ulong i = 0UL; i < net_cnt; i++ ) { - ulong tile_idx = fd_topo_find_tile( &config->topo, net_tile_name, i ); - if( FD_UNLIKELY( tile_idx == ULONG_MAX ) ) FD_LOG_ERR(( "net tile %lu not found", i )); - fd_topo_tile_t * tile = &config->topo.tiles[ tile_idx ]; - - ulong repair_net_in_idx = fd_topo_find_tile_in_link( &config->topo, tile, "repair_net", 0UL ); - if( FD_UNLIKELY( repair_net_in_idx == ULONG_MAX ) ) FD_LOG_ERR(( "repair_net link not found for net tile %lu", i )); - FD_TEST( tile->metrics ); - repair_net_links[i] = fd_metrics_link_in( tile->metrics, repair_net_in_idx ); - FD_TEST( repair_net_links[i] ); - - /* process all net_shred links */ - ulong shred_tile_idx = fd_topo_find_tile( &config->topo, "shred", 0 ); - if( FD_UNLIKELY( shred_tile_idx == ULONG_MAX ) ) FD_LOG_ERR(( "shred tile 0 not found" )); - fd_topo_tile_t * shred_tile = &config->topo.tiles[ shred_tile_idx ]; - - ulong shred_out_in_idx = fd_topo_find_tile_in_link( &config->topo, shred_tile, "net_shred", i ); - if( FD_UNLIKELY( shred_out_in_idx == ULONG_MAX ) ) FD_LOG_ERR(( "net_shred link not found for shred tile 0" )); - FD_TEST( shred_tile->metrics ); - net_shred_links[i] = fd_metrics_link_in( shred_tile->metrics, shred_out_in_idx ); - FD_TEST( net_shred_links[i] ); - } - - FD_LOG_NOTICE(( "Repair catchup run" )); - - ulong shred_out_link_idx = fd_topo_find_link( &config->topo, "shred_out", 0UL ); - FD_TEST( shred_out_link_idx!=ULONG_MAX ); - fd_topo_link_t * shred_out_link = &config->topo.links[ shred_out_link_idx ]; - fd_frag_meta_t * shred_out_mcache = shred_out_link->mcache; - void * shred_out_dcache = config->topo.workspaces[ config->topo.objs[ shred_out_link->dcache_obj_id ].wksp_id ].wksp; - - ulong turbine_slot0 = 0; - long last_print = fd_log_wallclock(); - ulong last_sent = 0UL; - - fd_topo_run_single_process( &config->topo, config->uid, config->gid, fdctl_tile_run ); - for(;;) { - - if( FD_UNLIKELY( !turbine_slot0 ) ) { - fd_frag_meta_t * frag = &shred_out_mcache[0]; /* hack to get first frag */ - if ( frag->sz > 0 ) { - uchar * shred_out_chunk = fd_chunk_to_laddr( shred_out_dcache, frag->chunk ); - fd_shred_base_t * shred_out_shred = (fd_shred_base_t *)fd_type_pun( shred_out_chunk ); - turbine_slot0 = shred_out_shred->shred.slot; - FD_LOG_NOTICE(("turbine_slot0: %lu", turbine_slot0)); - } - } - - /* print metrics */ - - long now = fd_log_wallclock(); - int catchup_finished = 0; - if( FD_UNLIKELY( now - last_print > 1e9L ) ) { - print_tile_metrics( shred_metrics, repair_metrics, repair_metrics_prev, repair_net_links, net_shred_links, net_cnt, &last_sent, last_print, now ); - ulong slots_behind = turbine_slot0 > repair_metrics[ MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ) ] ? turbine_slot0 - repair_metrics[ MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ) ] : 0; - printf(" Repaired slots: %lu/%lu (slots behind: %lu)\n", repair_metrics[ MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ) ], turbine_slot0, slots_behind ); - if( turbine_slot0 && !slots_behind ) { - catchup_finished = 1; - } - printf("\n"); - fflush( stdout ); - last_print = now; - } - - if( FD_UNLIKELY( catchup_finished ) ) { - /* repair cmd owned memory */ - location_table = fd_location_table_join( fd_location_table_new( location_table_mem ) ); - read_iptable( args->repair.iptable_path, location_table ); - print_peer_location_latency( repair_wksp->wksp, repair_ctx ); - print_catchup_slots( repair_wksp->wksp, repair_ctx, 0, 1 ); - FD_LOG_NOTICE(("Catchup to slot %lu completed successfully", turbine_slot0)); - fd_sys_util_exit_group( 0 ); - } - } -} - -/* Tests equivocation detection & repair path. */ -static void -repair_cmd_fn_eqvoc( args_t * args, - config_t * config ) { - (void)args; - memset( &config->topo, 0, sizeof(config->topo) ); - repair_topo( config ); - - FD_LOG_NOTICE(( "Repair eqvoc testing init" )); - fd_topo_print_log( 1, &config->topo ); - - args_t configure_args = { .configure.command = CONFIGURE_CMD_INIT, }; - for( ulong i=0UL; STAGES[ i ]; i++ ) configure_args.configure.stages[ i ] = STAGES[ i ]; - configure_cmd_fn( &configure_args, config ); - if( 0==strcmp( config->net.provider, "xdp" ) ) fd_topo_install_xdp_simple( &config->topo, config->net.bind_address_parsed ); - - run_firedancer_init( config, 1, 0 ); - fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - if( 0==strcmp( config->net.provider, "mlx5" ) ) { - fd_topo_install_mlx5( &config->topo, NULL ); - } - fd_topo_fill( &config->topo ); - - repair_load_manifest( &config->topo, args->repair.manifest_path ); - - ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "repair", 0UL ); - fd_topo_tile_t * repair_tile = &config->topo.tiles[ repair_tile_idx ]; - volatile ulong * repair_metrics = fd_metrics_tile( repair_tile->metrics ); - - void * scratch = fd_topo_obj_laddr( &config->topo, repair_tile->tile_obj_id ); - if( FD_UNLIKELY( !scratch ) ) FD_LOG_ERR(( "Failed to access repair tile scratch memory" )); - FD_SCRATCH_ALLOC_INIT( l, scratch ); - ctx_t * repair_ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) ); - (void)repair_ctx; - - /* read tower_out mcache dcache */ - ulong tower_out_link_idx = fd_topo_find_link( &config->topo, "tower_out", 0UL ); - FD_TEST( tower_out_link_idx!=ULONG_MAX ); - fd_topo_link_t * tower_out_link = &config->topo.links[ tower_out_link_idx ]; - fd_frag_meta_t * tower_out_mcache = tower_out_link->mcache; - fd_wksp_t * tower_out_mem = config->topo.workspaces[ config->topo.objs[ tower_out_link->dcache_obj_id ].wksp_id ].wksp; - ulong tower_out_chunk0 = fd_dcache_compact_chunk0( tower_out_mem, tower_out_link->dcache ); - ulong tower_out_wmark = fd_dcache_compact_wmark( tower_out_mem, tower_out_link->dcache, tower_out_link->mtu ); - ulong tower_out_chunk = tower_out_chunk0; - - fd_topo_run_single_process( &config->topo, config->uid, config->gid, fdctl_tile_run ); - int confirmed = 0; - for(;;) { - /* publish a confirmation on tower_out */ - if( FD_UNLIKELY( !confirmed && repair_metrics[ MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ) ] != 0 ) ) { - fd_tower_slot_confirmed_t * msg = fd_chunk_to_laddr( tower_out_mem, tower_out_chunk ); - FD_LOG_NOTICE(( "publishing confirmation for slot %lu", msg->slot )); - fd_mcache_publish( tower_out_mcache, tower_out_link->depth, 0, FD_TOWER_SIG_SLOT_CONFIRMED, tower_out_chunk, sizeof(fd_tower_slot_confirmed_t), 0, 0, 0 ); - tower_out_chunk = fd_dcache_compact_next( tower_out_chunk, sizeof(fd_tower_slot_confirmed_t), tower_out_chunk0, tower_out_wmark ); - confirmed = 1; - } - sleep( 1 ); - } -} - static void repair_cmd_fn_metrics( args_t * args, config_t * config ) { @@ -1353,22 +635,12 @@ repair_cmd_args( int * pargc, (*pargc)--; (*pargv)++; - /* required args */ - - char const * manifest_path = fd_env_strip_cmdline_cstr ( pargc, pargv, "--manifest-path", NULL, NULL ); - /* optional args */ char const * iptable_path = fd_env_strip_cmdline_cstr ( pargc, pargv, "--iptable", NULL, NULL ); ulong slot = fd_env_strip_cmdline_ulong ( pargc, pargv, "--slot", NULL, ULONG_MAX ); int sort_by_slot = fd_env_strip_cmdline_contains( pargc, pargv, "--sort-by-slot" ); - if( FD_UNLIKELY( !strcmp( args->repair.pos_arg, "catchup" ) && !manifest_path ) ) { - args->repair.help = 1; - return; - } - - fd_cstr_fini( fd_cstr_append_cstr_safe( fd_cstr_init( args->repair.manifest_path ), manifest_path, sizeof(args->repair.manifest_path)-1UL ) ); fd_cstr_fini( fd_cstr_append_cstr_safe( fd_cstr_init( args->repair.iptable_path ), iptable_path, sizeof(args->repair.iptable_path )-1UL ) ); args->repair.slot = slot; args->repair.sort_by_slot = sort_by_slot; @@ -1383,9 +655,7 @@ repair_cmd_fn( args_t * args, return; } - if ( !strcmp( args->repair.pos_arg, "catchup" ) ) repair_cmd_fn_catchup ( args, config ); - else if( !strcmp( args->repair.pos_arg, "eqvoc" ) ) repair_cmd_fn_eqvoc ( args, config ); - else if( !strcmp( args->repair.pos_arg, "forest" ) ) repair_cmd_fn_forest ( args, config ); + if ( !strcmp( args->repair.pos_arg, "forest" ) ) repair_cmd_fn_forest ( args, config ); else if( !strcmp( args->repair.pos_arg, "inflight" ) ) repair_cmd_fn_inflight ( args, config ); else if( !strcmp( args->repair.pos_arg, "requests" ) ) repair_cmd_fn_requests ( args, config ); else if( !strcmp( args->repair.pos_arg, "waterfall" ) ) repair_cmd_fn_waterfall( args, config ); @@ -1396,9 +666,6 @@ repair_cmd_fn( args_t * args, static void repair_args_help( fd_action_help_t * help ) { - fd_action_help_arg( help, "catchup", NULL, "Run a reduced topology that only repairs slots until catchup.\n" - "Requires --manifest-path; accepts --iptable and --sort-by-slot" ); - fd_action_help_arg( help, "eqvoc", NULL, "Test equivocation detection and the repair path" ); fd_action_help_arg( help, "forest", NULL, "Print the repair forest. Accepts --slot to drill into a slot" ); fd_action_help_arg( help, "inflight", NULL, "Print the inflight repairs" ); fd_action_help_arg( help, "requests", NULL, "Print the queued repair requests" ); @@ -1406,10 +673,9 @@ repair_args_help( fd_action_help_t * help ) { "response latencies. Accepts --iptable and --sort-by-slot" ); fd_action_help_arg( help, "peers", NULL, "Print the list of slow and fast repair peers" ); fd_action_help_arg( help, "metrics", NULL, "Print repair tile metrics in a digestible format" ); - fd_action_help_arg( help, "--manifest-path", "", "Path to manifest file (required by catchup)" ); - fd_action_help_arg( help, "--iptable", "", "Path to iptable file (catchup, waterfall)" ); + fd_action_help_arg( help, "--iptable", "", "Path to iptable file (waterfall)" ); fd_action_help_arg( help, "--slot", "", "Specific forest slot to drill into (forest)" ); - fd_action_help_arg( help, "--sort-by-slot", NULL, "Sort results by slot (catchup, waterfall)" ); + fd_action_help_arg( help, "--sort-by-slot", NULL, "Sort results by slot (waterfall)" ); } action_t fd_action_repair = { @@ -1417,10 +683,10 @@ action_t fd_action_repair = { .args = repair_cmd_args, .fn = repair_cmd_fn, .perm = dev_cmd_perm, - .description = "Spawn a reduced topology for inspecting and profiling the repair tile", - .detail = "Boots a smaller Firedancer topology focused on the repair tile and runs the\n" - "requested subcommand to drive or inspect repair behavior. Pick one of the\n" + .description = "Inspect the repair tile of a running validator", + .detail = "Attaches to the repair tile of a running Firedancer validator and runs the\n" + "requested subcommand to inspect repair behavior. Pick one of the\n" "subcommands below.", - .usage = "repair [OPTIONS]", + .usage = "repair [OPTIONS]", .args_help = repair_args_help, }; diff --git a/src/app/shared/fd_action.h b/src/app/shared/fd_action.h index 9689c72f348..b3cbac0c84e 100644 --- a/src/app/shared/fd_action.h +++ b/src/app/shared/fd_action.h @@ -115,7 +115,6 @@ union fdctl_args { /* options */ - char manifest_path[256UL]; char iptable_path[256UL]; ulong slot; int sort_by_slot; diff --git a/src/disco/gui/fd_gui.c b/src/disco/gui/fd_gui.c index 74f8522c761..28571468220 100644 --- a/src/disco/gui/fd_gui.c +++ b/src/disco/gui/fd_gui.c @@ -2412,7 +2412,8 @@ fd_gui_handle_epoch_info( fd_gui_t * gui, epoch_info->start_slot, epoch_info->slot_cnt, epoch_info->staked_vote_cnt, - gui->epoch.stakes_scratch ) ); + gui->epoch.stakes_scratch, + fd_epoch_leaders_use_geo_aware( &epoch_info->features, &epoch_info->epoch_schedule, epoch_info->epoch ) ) ); FD_TEST( lsched ); int created = 0; diff --git a/src/disco/shred/fd_stake_ci.c b/src/disco/shred/fd_stake_ci.c index a583ec18cf0..2006827ca62 100644 --- a/src/disco/shred/fd_stake_ci.c +++ b/src/disco/shred/fd_stake_ci.c @@ -30,7 +30,7 @@ fd_stake_ci_new( void * mem, ei->start_slot = 0UL; ei->slot_cnt = 0UL; - ei->lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( ei->_lsched, 0UL, 0UL, 1UL, 1UL, info->vote_stake_weight ) ); + ei->lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( ei->_lsched, 0UL, 0UL, 1UL, 1UL, info->vote_stake_weight, 0 ) ); ei->sdest = fd_shred_dest_join ( fd_shred_dest_new ( ei->_sdest, info->shred_dest, 1UL, ei->lsched, identity_key, seed ) ); } info->identity_key[ 0 ] = *identity_key; @@ -66,6 +66,7 @@ fd_stake_ci_stake_msg_init( fd_stake_ci_t * info, info->scratch->slot_cnt = msg->slot_cnt; info->scratch->staked_vote_cnt = msg->staked_vote_cnt; info->scratch->staked_id_cnt = msg->staked_id_cnt; + info->scratch->geo_aware_lsched = 0; fd_memcpy( info->vote_stake_weight, fd_stake_weight_msg_stake_weights( msg ), msg->staked_vote_cnt*sizeof(fd_vote_stake_weight_t) ); fd_memcpy( info->stake_weight, fd_stake_weight_msg_id_weights( msg ), msg->staked_id_cnt*sizeof(fd_stake_weight_t) ); @@ -93,6 +94,7 @@ fd_stake_ci_epoch_msg_init( fd_stake_ci_t * info, info->scratch->slot_cnt = msg->slot_cnt; info->scratch->staked_vote_cnt = msg->staked_vote_cnt; info->scratch->staked_id_cnt = msg->staked_id_cnt; + info->scratch->geo_aware_lsched = fd_epoch_leaders_use_geo_aware( &msg->features, &msg->epoch_schedule, msg->epoch ); fd_memcpy( info->vote_stake_weight, fd_epoch_info_msg_stake_weights( msg ), msg->staked_vote_cnt*sizeof(fd_vote_stake_weight_t) ); fd_memcpy( info->stake_weight, fd_epoch_info_msg_id_weights( msg ), msg->staked_id_cnt*sizeof(fd_stake_weight_t) ); @@ -197,7 +199,7 @@ fd_stake_ci_stake_msg_fini( fd_stake_ci_t * info ) { new_ei->slot_cnt = info->scratch->slot_cnt; new_ei->lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( new_ei->_lsched, epoch, new_ei->start_slot, new_ei->slot_cnt, - info->scratch->staked_vote_cnt, info->vote_stake_weight ) ); + info->scratch->staked_vote_cnt, info->vote_stake_weight, info->scratch->geo_aware_lsched ) ); new_ei->sdest = fd_shred_dest_join ( fd_shred_dest_new ( new_ei->_sdest, info->shred_dest, j, new_ei->lsched, info->identity_key, info->seed ) ); log_summary( "stake update", info ); diff --git a/src/disco/shred/fd_stake_ci.h b/src/disco/shred/fd_stake_ci.h index 341184c452c..1e97c6b6336 100644 --- a/src/disco/shred/fd_stake_ci.h +++ b/src/disco/shred/fd_stake_ci.h @@ -55,6 +55,7 @@ struct fd_stake_ci { ulong slot_cnt; ulong staked_vote_cnt; ulong staked_id_cnt; + int geo_aware_lsched; } scratch[1]; fd_vote_stake_weight_t vote_stake_weight[ MAX_STAKE_WEIGHTS ]; diff --git a/src/disco/shred/test_shred_dest.c b/src/disco/shred/test_shred_dest.c index e3f5f2d0f91..6048e7bed58 100644 --- a/src/disco/shred/test_shred_dest.c +++ b/src/disco/shred/test_shred_dest.c @@ -36,7 +36,7 @@ test_compute_first_matches_agave( void ) { FD_TEST( fd_shred_dest_footprint ( staked, staked-cnt ) <= TEST_MAX_FOOTPRINT ); FD_TEST( fd_epoch_leaders_footprint( cnt, 10000UL ) <= TEST_MAX_FOOTPRINT ); - fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 10000UL, staked, stakes ) ); + fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 10000UL, staked, stakes, 0 ) ); FD_TEST( lsched ); fd_shred_dest_t * sdest = fd_shred_dest_join( fd_shred_dest_new( _sd_footprint, info, cnt, lsched, src_key, TEST_HASH_SEED ) ); @@ -89,7 +89,7 @@ test_compute_children_matches_agave( void ) { FD_TEST( fd_shred_dest_footprint ( staked, cnt-staked ) <= TEST_MAX_FOOTPRINT ); FD_TEST( fd_epoch_leaders_footprint( cnt, 2000UL ) <= TEST_MAX_FOOTPRINT ); - fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 4000UL, staked, stakes ) ); + fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 4000UL, staked, stakes, 0 ) ); fd_shred_dest_t * sdest = fd_shred_dest_join( fd_shred_dest_new( _sd_footprint, info, cnt, lsched, src_key, TEST_HASH_SEED ) ); @@ -193,7 +193,7 @@ test_batching( void ) { info[i].ip4 = (uint)i; } fd_pubkey_t * src_key = &(info[0].pubkey); - fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 100UL, cnt, stakes ) ); + fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 100UL, cnt, stakes, 0 ) ); fd_shred_dest_t * sdest = fd_shred_dest_join( fd_shred_dest_new( _sd_footprint, info, cnt, lsched, src_key, TEST_HASH_SEED ) ); #define BATCH_CNT 5 @@ -270,7 +270,7 @@ test_vary_stake( void ) { stakes[30].stake = prev-1UL; stakes[31].stake = prev-1UL; - fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 100UL, cnt, stakes ) ); + fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 100UL, cnt, stakes, 0 ) ); test_distribution_is_tree( info, 32UL, lsched, 6+fd_rng_ulong_roll(r, 25UL ), fd_rng_ulong_roll( r, 100UL ), fd_rng_int_roll( r, 2 ), fd_rng_ulong_roll( r, 100UL ) ); fd_epoch_leaders_delete( fd_epoch_leaders_leave( lsched ) ); } @@ -291,7 +291,7 @@ test_t1_vary_radix( void ) { staked += (info[i].stake_lamports>0UL); } - fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 4000UL, staked, stakes ) ); + fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 4000UL, staked, stakes, 0 ) ); for( ulong fanout=35UL; fanout<650UL; fanout+=11UL ) { FD_LOG_NOTICE(( "Fanout: %lu", fanout )); test_distribution_is_tree( info, cnt, lsched, fanout, fd_rng_ulong_roll( r, 4000UL ), fd_rng_int_roll( r, 2 ), fd_rng_ulong_roll( r, 100UL ) ); @@ -317,7 +317,7 @@ test_unstaked_no_dest( void ) { stakes[i].stake = info[i].stake_lamports; } fd_pubkey_t const * src_key = &info[ 30 ].pubkey; /* unstaked */ - fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 100UL, 24UL, stakes ) ); + fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 100UL, 24UL, stakes, 0 ) ); fd_shred_dest_t * sdest = fd_shred_dest_join( fd_shred_dest_new( _sd_footprint, info, cnt, lsched, src_key, TEST_HASH_SEED ) ); FD_TEST( sdest ); @@ -378,7 +378,7 @@ test_change_contact( void ) { staked += (info[i].stake_lamports>0UL); } - fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 10000UL, staked, stakes ) ); + fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 10000UL, staked, stakes, 0 ) ); fd_shred_dest_t * sdest = fd_shred_dest_join( fd_shred_dest_new( _sd_footprint, info, cnt, lsched, src_key, TEST_HASH_SEED ) ); fd_shred_dest_idx_to_dest( sdest, (ushort)0 )->ip4 = 12U; @@ -399,7 +399,7 @@ test_errors( void ) { memset( &(stakes[0].vote_key), 1, 32UL ); stakes[0].stake = 100UL; fd_pubkey_t const * src_key = (fd_pubkey_t const *)t1_pubkey; - fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 10000UL, 1UL, stakes ) ); + fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 10000UL, 1UL, stakes, 0 ) ); fd_shred_dest_t * sdest = fd_shred_dest_join( fd_shred_dest_new( _sd_footprint, NULL, 0UL, lsched, src_key, TEST_HASH_SEED ) ); FD_TEST( sdest==NULL ); @@ -436,7 +436,7 @@ perf_run( char const * name, FD_TEST( fd_epoch_leaders_footprint( staked, 10000UL ) <= TEST_MAX_FOOTPRINT ); fd_pubkey_t const * src = &info[ src_rank ].pubkey; - fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 10000UL, staked, stakes ) ); + fd_epoch_leaders_t * lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( _l_footprint, 0UL, 0UL, 10000UL, staked, stakes, 0 ) ); fd_shred_dest_t * sdest = fd_shred_dest_join ( fd_shred_dest_new ( _sd_footprint, info, cnt, lsched, src, TEST_HASH_SEED ) ); FD_TEST( lsched && sdest ); diff --git a/src/disco/shred/test_stake_ci.c b/src/disco/shred/test_stake_ci.c index 512cd476b6d..c8a6a3d0e71 100644 --- a/src/disco/shred/test_stake_ci.c +++ b/src/disco/shred/test_stake_ci.c @@ -1,4 +1,5 @@ #include "fd_stake_ci.h" +#include "../../flamenco/leaders/fd_multi_epoch_leaders.h" #define SLOTS_PER_EPOCH 1000 /* Just for testing */ #define TEST_HASH_SEED (0x9b6d1f47a2c583e0UL) @@ -45,6 +46,7 @@ generate_epoch_msg( uchar * _buf, buf->slot_cnt = SLOTS_PER_EPOCH; buf->staked_vote_cnt = strlen(stakers); memset( &buf->features, 0, sizeof(fd_features_t) ); + buf->features.geo_aware_leader_schedule = FD_FEATURE_DISABLED; fd_vote_stake_weight_t * weights = fd_epoch_info_msg_stake_weights( buf ); for( ulong i=0UL; istaked_vote_cnt; i++ ) { @@ -937,6 +939,50 @@ test_dest_remove( void ) { fd_stake_ci_delete( fd_stake_ci_leave( info ) ); } +static uchar mleaders_mem[ FD_MULTI_EPOCH_LEADERS_FOOTPRINT ] __attribute__((aligned(FD_MULTI_EPOCH_LEADERS_ALIGN))); +static uchar ref_mem [ FD_EPOCH_LEADERS_FOOTPRINT( MAX_STAKE_WEIGHTS, SLOTS_PER_EPOCH ) ] __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))); +static uchar base_mem [ FD_EPOCH_LEADERS_FOOTPRINT( MAX_STAKE_WEIGHTS, SLOTS_PER_EPOCH ) ] __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))); + +static void +test_geo_aware_consistency( void ) { + static fd_validator_location_t const city[ 4 ] = { + { 4051542, 618526, 4870645 }, { 1333729, -4654333, 4138065 }, + { -3955214, 3355452, 3699409 }, { -4646017, 2553114, -3534483 } + }; + fd_stake_ci_t * info = fd_stake_ci_join( fd_stake_ci_new( _info, identity_key, TEST_HASH_SEED ) ); + fd_multi_epoch_leaders_t * mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( mleaders_mem ) ); + + fd_epoch_info_msg_t * msg = generate_epoch_msg( epoch_msg, 1UL, "ABCDEFGH" ); + fd_vote_stake_weight_t * w = fd_epoch_info_msg_stake_weights( msg ); + for( ulong i=0UL; istaked_vote_cnt; i++ ) w[ i ].location = city[ i%4UL ]; + msg->features.geo_aware_leader_schedule = 0UL; + FD_TEST( fd_epoch_leaders_use_geo_aware( &msg->features, &msg->epoch_schedule, msg->epoch ) ); + + static fd_vote_stake_weight_t copy[ 8 ]; + memcpy( copy, w, msg->staked_vote_cnt*sizeof(fd_vote_stake_weight_t) ); + fd_epoch_leaders_t * ref = fd_epoch_leaders_join( fd_epoch_leaders_new( ref_mem, 1UL, msg->start_slot, msg->slot_cnt, msg->staked_vote_cnt, copy, 1 ) ); + fd_epoch_leaders_t * base = fd_epoch_leaders_join( fd_epoch_leaders_new( base_mem, 1UL, msg->start_slot, msg->slot_cnt, msg->staked_vote_cnt, copy, 0 ) ); + FD_TEST( ref && base ); + FD_TEST( memcmp( ref->sched, base->sched, ref->sched_cnt*sizeof(uint) ) ); + + fd_stake_ci_epoch_msg_init( info, msg ); + fd_stake_ci_epoch_msg_fini( info ); + fd_multi_epoch_leaders_epoch_msg_init( mleaders, msg ); + fd_multi_epoch_leaders_epoch_msg_fini( mleaders ); + + fd_epoch_leaders_t const * ci = fd_stake_ci_get_lsched_for_slot( info, msg->start_slot ); + fd_epoch_leaders_t const * ml = fd_multi_epoch_leaders_get_lsched_for_epoch( mleaders, 1UL ); + FD_TEST( ci && ml ); + FD_TEST( ci->sched_cnt==ref->sched_cnt && ml->sched_cnt==ref->sched_cnt ); + FD_TEST( !memcmp( ci->sched, ref->sched, ref->sched_cnt*sizeof(uint) ) ); + FD_TEST( !memcmp( ml->sched, ref->sched, ref->sched_cnt*sizeof(uint) ) ); + FD_TEST( !memcmp( ci->vote_addr, ref->vote_addr, ref->sched_cnt*sizeof(fd_pubkey_t) ) ); + FD_TEST( !memcmp( ml->vote_addr, ref->vote_addr, ref->sched_cnt*sizeof(fd_pubkey_t) ) ); + + fd_multi_epoch_leaders_delete( fd_multi_epoch_leaders_leave( mleaders ) ); + fd_stake_ci_delete( fd_stake_ci_leave( info ) ); +} + int main( int argc, char ** argv ) { @@ -954,6 +1000,7 @@ main( int argc, test_stake_msg(); test_epoch_msg(); + test_geo_aware_consistency(); test_changing_contact_info(); test_limits(); diff --git a/src/discof/backup/fd_ssmanifest_encoder.c b/src/discof/backup/fd_ssmanifest_encoder.c index 3cedf8d21b8..0cc06c9df58 100644 --- a/src/discof/backup/fd_ssmanifest_encoder.c +++ b/src/discof/backup/fd_ssmanifest_encoder.c @@ -224,13 +224,14 @@ ENCODE_FN { /* A zeroed key means no BLS key is registered (serialized as None) */ static uchar const no_bls_key[ FD_BLS_PUB_COMPRESSED_SZ ] = {0}; - int has_bls = !fd_memeq( ele->bls_key, no_bls_key, FD_BLS_PUB_COMPRESSED_SZ ); + int has_bls = !fd_memeq( ele->bls_key, no_bls_key, FD_BLS_PUB_COMPRESSED_SZ ); + int has_location = ele->location.x || ele->location.y || ele->location.z; enc->total_stake += ele->stake; FD_TEST( ec_cnt<=FD_EPOCH_CREDITS_MAX ); - ulong data_length = 186UL + fd_ulong_if( !!has_bls, FD_BLS_PUB_COMPRESSED_SZ, 0UL ) + 24UL * ec_cnt; + ulong data_length = fd_ulong_if( has_location, FD_VOTE_STATE_V5_SZ, 186UL + fd_ulong_if( !!has_bls, FD_BLS_PUB_COMPRESSED_SZ, 0UL ) + 24UL * ec_cnt ); /* Vote account key + stake */ PUSH_VAL( fd_pubkey_t, ele->pubkey ); @@ -240,43 +241,78 @@ ENCODE_FN { PUSH_VAL( ulong, 0UL ); PUSH_VAL( ulong, data_length ); - /* VoteStateV4 */ - PUSH_VAL( uint, 3U ); /* variant = V4 */ - PUSH_VAL( fd_pubkey_t, ele->node_account ); /* node_pubkey */ - PUSH_VAL( fd_pubkey_t, (fd_pubkey_t){0} ); /* authorized_withdrawer */ - PUSH_VAL( fd_pubkey_t, inflation_collector ); /* inflation_rewards_collector */ - PUSH_VAL( fd_pubkey_t, block_collector ); /* block_revenue_collector */ - PUSH_VAL( ushort, ele->commission ); /* inflation_rewards_commission_bps */ - PUSH_VAL( ushort, ele->block_revenue_commission_bps ); /* block_revenue_commission_bps */ - PUSH_VAL( ulong, ele->pending_delegator_rewards ); /* pending_delegator_rewards */ - if( has_bls ) { - typedef struct { uchar b[ FD_BLS_PUB_COMPRESSED_SZ ]; } bls_key_compressed_t; - PUSH_VAL( uchar, 1 ); /* bls_pubkey_compressed = Some */ - PUSH_VAL( bls_key_compressed_t, *(bls_key_compressed_t const *)ele->bls_key ); + if( has_location ) { + ulong epoch_key = epoch_stakes_key( bank, enc->epoch_idx ); + fd_vote_state_v5_t v5 = { + .node_pubkey = ele->node_account, + .inflation_rewards_collector = inflation_collector, + .block_revenue_collector = block_collector, + .inflation_rewards_commission_bps_len = 1, + .block_revenue_commission_bps_len = 1, + .pending_delegator_rewards = ele->pending_delegator_rewards, + .location = ele->location, + .inflation_rewards_commission_bps_epochs = { epoch_key }, + .inflation_rewards_commission_bps = { ele->commission }, + .block_revenue_commission_bps_epochs = { epoch_key }, + .block_revenue_commission_bps = { ele->block_revenue_commission_bps }, + .latest_vote = ULONG_MAX, + .root_slot = ULONG_MAX, + .accumulated_lamports_len = (uint)ec_cnt + }; + memcpy( v5.bls_pubkey_compressed, ele->bls_key, FD_BLS_PUB_COMPRESSED_SZ ); + for( ulong k=0UL, j=0UL; kepoch[j], + ec->base_credits + (ulong)ec->credits_delta[j], + ec->base_credits + (ulong)ec->prev_credits_delta[j] }; + j++; + } + typedef struct { uchar b[ FD_VOTE_STATE_V5_PAD_SZ ]; } v5_tail_t; + PUSH_VAL( uint, fd_vote_state_versioned_enum_v5 ); + PUSH_VAL( fd_vote_state_v5_t, v5 ); + PUSH_VAL( v5_tail_t, (v5_tail_t){0} ); } else { - PUSH_VAL( uchar, 0 ); /* bls_pubkey_compressed = None */ - } - PUSH_VAL( ulong, 0UL ); /* votes_length = 0 */ - PUSH_VAL( uchar, 0 ); /* root_slot = None */ - PUSH_VAL( ulong, 0UL ); /* authorized_voters_length = 0 */ - - /* Epoch credits */ - PUSH_VAL( ulong, ec_cnt ); - for( ulong k=0UL, j=0UL; knode_account ); /* node_pubkey */ + PUSH_VAL( fd_pubkey_t, (fd_pubkey_t){0} ); /* authorized_withdrawer */ + PUSH_VAL( fd_pubkey_t, inflation_collector ); /* inflation_rewards_collector */ + PUSH_VAL( fd_pubkey_t, block_collector ); /* block_revenue_collector */ + PUSH_VAL( ushort, ele->commission ); /* inflation_rewards_commission_bps */ + PUSH_VAL( ushort, ele->block_revenue_commission_bps ); /* block_revenue_commission_bps */ + PUSH_VAL( ulong, ele->pending_delegator_rewards ); /* pending_delegator_rewards */ + if( has_bls ) { + typedef struct { uchar b[ FD_BLS_PUB_COMPRESSED_SZ ]; } bls_key_compressed_t; + PUSH_VAL( uchar, 1 ); /* bls_pubkey_compressed = Some */ + PUSH_VAL( bls_key_compressed_t, *(bls_key_compressed_t const *)ele->bls_key ); + } else { + PUSH_VAL( uchar, 0 ); /* bls_pubkey_compressed = None */ + } + PUSH_VAL( ulong, 0UL ); /* votes_length = 0 */ + PUSH_VAL( uchar, 0 ); /* root_slot = None */ + PUSH_VAL( ulong, 0UL ); /* authorized_voters_length = 0 */ + + /* Epoch credits */ + PUSH_VAL( ulong, ec_cnt ); + for( ulong k=0UL, j=0UL; kepoch[j] ); + PUSH_VAL( ulong, ec->base_credits + (ulong)ec->credits_delta[j] ); + PUSH_VAL( ulong, ec->base_credits + (ulong)ec->prev_credits_delta[j] ); + j++; } - PUSH_VAL( ulong, (ulong)ec->epoch[j] ); - PUSH_VAL( ulong, ec->base_credits + (ulong)ec->credits_delta[j] ); - PUSH_VAL( ulong, ec->base_credits + (ulong)ec->prev_credits_delta[j] ); - j++; - } - PUSH_VAL( ulong, 0UL ); /* last_timestamp_slot */ - PUSH_VAL( ulong, 0UL ); /* last_timestamp_timestamp */ + PUSH_VAL( ulong, 0UL ); /* last_timestamp_slot */ + PUSH_VAL( ulong, 0UL ); /* last_timestamp_timestamp */ + } /* AccountSharedData trailer */ PUSH_VAL( fd_pubkey_t, fd_solana_vote_program_id ); /* owner */ diff --git a/src/discof/backup/test_snap_roundtrip.c b/src/discof/backup/test_snap_roundtrip.c index 2e7c782d87e..987449aa53c 100644 --- a/src/discof/backup/test_snap_roundtrip.c +++ b/src/discof/backup/test_snap_roundtrip.c @@ -34,6 +34,13 @@ #define TEST_WKSP_ADDL_PART_CNT (64UL) static fd_validator_location_t const LOC_FRA = { 4051542, 618526, 4870645 }; +static fd_validator_location_t const LOC_TYO = { -3955214, 3355452, 3699409 }; + +static int +loc_eq( fd_validator_location_t a, + fd_validator_location_t b ) { + return a.x==b.x && a.y==b.y && a.z==b.z; +} /* The writer emits one slot delta per recent slot with a block, so the parser must accept at least that many. */ @@ -319,6 +326,7 @@ test_manifest_roundtrip( fd_svm_mini_t * mini, bank->vote_stakes_fork_id = fd_vote_stakes_init( vote_stakes, bank->f.epoch ); fork_id = bank->vote_stakes_fork_id; uchar const no_bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ] = {0}; + fd_validator_location_t const no_loc = {0}; /* Non-default SIMD-0123 fields and SIMD-0232 collectors: distinct inflation and block collectors for vote0 in the t_1 set, a @@ -334,14 +342,14 @@ test_manifest_roundtrip( fd_svm_mini_t * mini, int is1 = i==1UL; fd_vote_stakes_snap_insert_t_1( vote_stakes, fork_id, &votes[i], &identities[i], 1000000000UL, 1234U, no_bls, is0 ? 2500U : FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, is0 ? 777UL : 0UL, - is0 ? &infl0 : NULL, is0 ? &blk0 : NULL ); + is0 ? &infl0 : NULL, is0 ? &blk0 : NULL, is0 ? &LOC_FRA : &no_loc ); fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &votes[i], &identities[i], 1000000000UL, 1234U, no_bls, is1 ? 1234U : FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, is1 ? 55UL : 0UL, - NULL, is1 ? &blk1 : NULL ); + NULL, is1 ? &blk1 : NULL, is1 ? &LOC_TYO : &no_loc ); for( ulong n=3UL; n<=5UL; n++ ) { int t3_0 = is0 && n==3UL; fd_vote_stakes_snap_insert_t_n( vote_stakes, fork_id, n, &votes[i], &identities[i], 1000000UL*n, (ushort)(100UL*n), no_bls, - t3_0 ? 4321U : FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, t3_0 ? 99UL : 0UL, NULL, NULL ); + t3_0 ? 4321U : FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, t3_0 ? 99UL : 0UL, NULL, NULL, t3_0 ? &LOC_TYO : &no_loc ); } } @@ -370,9 +378,9 @@ test_manifest_roundtrip( fd_svm_mini_t * mini, ulong const late_epoch = bank->f.epoch+1UL; put_vote_account( mini, &extra_vote, &identities[0], 1UL, voter_epochs, &extra_voter ); put_vote_account( mini, &late_vote, &late_node, 1UL, &late_epoch, &late_voter ); - fd_vote_stakes_snap_insert_t_n( vote_stakes, fork_id, 3UL, &extra_vote, &identities[0], 3000000UL, 300U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_n( vote_stakes, fork_id, 3UL, &phantom_vote, &phantom_node, 3000000UL, 300U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_n( vote_stakes, fork_id, 3UL, &late_vote, &late_node, 3000000UL, 300U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, fork_id, 3UL, &extra_vote, &identities[0], 3000000UL, 300U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, fork_id, 3UL, &phantom_vote, &phantom_node, 3000000UL, 300U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, fork_id, 3UL, &late_vote, &late_node, 3000000UL, 300U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); seed_epoch_credits( bank ); @@ -508,6 +516,9 @@ test_manifest_roundtrip( fd_svm_mini_t * mini, FD_STORE( ulong, buf+stake_delegations_len_off, 1UL ); memset( manifest, 0, sizeof(fd_snapshot_manifest_t) ); + for( ulong i=0UL; iepoch_stakes[ i ].vote_stakes[ j ].location = (fd_validator_location_t){ 1, 1, 1 }; + } fd_ssmanifest_parser_init( parser, manifest ); int result = fd_ssmanifest_parser_consume( parser, buf, total_written ); @@ -557,6 +568,7 @@ test_manifest_roundtrip( fd_svm_mini_t * mini, FD_TEST( !memcmp( vs->commission_inflation, &infl0, 32UL ) ); FD_TEST( !memcmp( vs->commission_block, &blk0, 32UL ) ); FD_TEST( vs->commission_block_bps==2500U && vs->pending_delegator_rewards==777UL ); + FD_TEST( loc_eq( vs->location, LOC_FRA ) ); seen_t1_vote0 = 1; } else { /* default collectors: inflation is the vote account, block is @@ -565,6 +577,7 @@ test_manifest_roundtrip( fd_svm_mini_t * mini, FD_TEST( !memcmp( vs->commission_block, vs->identity, 32UL ) ); FD_TEST( vs->commission_block_bps==FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS ); FD_TEST( vs->pending_delegator_rewards==0UL ); + FD_TEST( loc_eq( vs->location, no_loc ) ); if( !memcmp( vs->vote, &vote1, 32UL ) ) seen_t1_vote1 = 1; } } @@ -579,12 +592,14 @@ test_manifest_roundtrip( fd_svm_mini_t * mini, FD_TEST( !memcmp( vs->commission_inflation, vs->vote, 32UL ) ); FD_TEST( !memcmp( vs->commission_block, &blk1, 32UL ) ); FD_TEST( vs->commission_block_bps==1234U && vs->pending_delegator_rewards==55UL ); + FD_TEST( loc_eq( vs->location, LOC_TYO ) ); seen_t2_vote1 = 1; } else { FD_TEST( !memcmp( vs->commission_inflation, vs->vote, 32UL ) ); FD_TEST( !memcmp( vs->commission_block, vs->identity, 32UL ) ); FD_TEST( vs->commission_block_bps==FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS ); FD_TEST( vs->pending_delegator_rewards==0UL ); + FD_TEST( loc_eq( vs->location, no_loc ) ); if( !memcmp( vs->vote, &vote0, 32UL ) ) seen_t2_vote0 = 1; } } @@ -609,9 +624,11 @@ test_manifest_roundtrip( fd_svm_mini_t * mini, FD_TEST( vs->stake==1000000UL*n && vs->commission==100UL*n ); if( n==3UL && !memcmp( vs->vote, &vote0, 32UL ) ) { FD_TEST( vs->commission_block_bps==4321U && vs->pending_delegator_rewards==99UL ); + FD_TEST( loc_eq( vs->location, LOC_TYO ) ); } else { FD_TEST( vs->commission_block_bps==FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS ); FD_TEST( vs->pending_delegator_rewards==0UL ); + FD_TEST( loc_eq( vs->location, no_loc ) ); } int found = 0; @@ -823,6 +840,10 @@ test_manifest_v5_vote_accounts( fd_svm_mini_t * mini, fd_snapshot_manifest_vote_stakes_t vs = b->epoch_stakes[ i ].vote_stakes[ j ]; FD_TEST( vs.has_identity_bls==1 ); vs.has_identity_bls = a->epoch_stakes[ i ].vote_stakes[ j ].has_identity_bls; + fd_validator_location_t const la = a->epoch_stakes[ i ].vote_stakes[ j ].location; + if( la.x || la.y || la.z ) FD_TEST( loc_eq( vs.location, la ) ); + else FD_TEST( loc_eq( vs.location, LOC_FRA ) ); + vs.location = la; FD_TEST( fd_memeq( &vs, &a->epoch_stakes[ i ].vote_stakes[ j ], sizeof(fd_snapshot_manifest_vote_stakes_t) ) ); } } diff --git a/src/discof/execrp/test_execrp_tile.c b/src/discof/execrp/test_execrp_tile.c index 1842c361209..998a1047f5c 100644 --- a/src/discof/execrp/test_execrp_tile.c +++ b/src/discof/execrp/test_execrp_tile.c @@ -27,6 +27,8 @@ static fd_svm_mini_t * mini; static fd_topo_t topo[1]; static uchar metrics_scratch[ FD_METRICS_FOOTPRINT( 0UL ) ] __attribute__((aligned(FD_METRICS_ALIGN))); +static fd_validator_location_t const no_loc = {0}; + struct test_env { void * tile_mem; fd_svm_mini_t * mini; @@ -503,7 +505,7 @@ FD_UNIT_TEST( execrp_cert_verify ) { uchar bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; blst_p1_affine_compress( bls, pub ); fd_pubkey_t vote = { .ul = { 0x7000UL+i } }; fd_pubkey_t node = { .ul = { 0x8000UL+i } }; - fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &vote, &node, 1000UL, 0U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &vote, &node, 1000UL, 0U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); } fd_vote_stakes_finalize( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2 ); diff --git a/src/discof/replay/test_replay_tile.c b/src/discof/replay/test_replay_tile.c index e268accdfe8..76bec275484 100644 --- a/src/discof/replay/test_replay_tile.c +++ b/src/discof/replay/test_replay_tile.c @@ -30,6 +30,8 @@ #define TEST_REPAIR_IN_IDX 0UL #define TEST_EXECRP_IN_IDX 1UL +static fd_validator_location_t const no_loc = {0}; + /* ---- Mock store ---- */ static fd_store_fec_t mock_store_fec; @@ -420,6 +422,7 @@ setup_mleaders( fd_replay_tile_t * ctx ) { for( ulong epoch=0UL; epoch<2UL; epoch++ ) { memset( buf, 0, sizeof(buf) ); fd_epoch_info_msg_t * msg = (fd_epoch_info_msg_t *)buf; + msg->features.geo_aware_leader_schedule = FD_FEATURE_DISABLED; msg->epoch = epoch; msg->staked_vote_cnt = 1UL; msg->staked_id_cnt = 1UL; @@ -1073,7 +1076,7 @@ test_snapmk_start_leader_after_epoch_rollover( fd_wksp_t * wksp ) { fd_vote_stake_weight_t w2[ 1 ] = { { .stake = 1UL } }; memset( w2[ 0 ].vote_key.uc, 0x22, 32UL ); memset( w2[ 0 ].id_key.uc, 0x22, 32UL ); - FD_TEST( fd_epoch_leaders_join( fd_epoch_leaders_new( fd_bank_epoch_leaders_modify( bank, 0UL ), 2UL, 256UL, 128UL, 1UL, w2 ) ) ); + FD_TEST( fd_epoch_leaders_join( fd_epoch_leaders_new( fd_bank_epoch_leaders_modify( bank, 0UL ), 2UL, 256UL, 128UL, 1UL, w2, 0 ) ) ); FD_TEST( !fd_epoch_leaders_get( fd_bank_epoch_leaders_query( bank, 0UL ), 127UL ) ); fd_pubkey_t expected; memset( expected.uc, 0x11, 32UL ); @@ -1996,7 +1999,7 @@ test_ag_update_delinquent( fd_wksp_t * wksp ) { fd_pubkey_t node = (fd_pubkey_t){ .ul = { 2000UL+i } }; uchar bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; test_bls_pubkey( bls, i ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &vote[ i ], &node, stake[ i ], 0U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &vote[ i ], &node, stake[ i ], 0U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_update_state( vote_stakes, fork_id, &vote[ i ], 0UL, 0L, 1 ); } fd_vote_stakes_finalize( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2 ); @@ -2126,7 +2129,7 @@ test_update_delinquent_stake( fd_wksp_t * wksp ) { fd_pubkey_t node = (fd_pubkey_t){ .ul = { 2000UL+i } }; uchar bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; test_bls_pubkey( bls, i ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &vote[ i ], &node, stake[ i ], 0U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &vote[ i ], &node, stake[ i ], 0U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); } fd_vote_stakes_finalize( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2 ); @@ -2423,7 +2426,7 @@ test_footer_cert_tasks( fd_wksp_t * wksp ) { fd_pubkey_t node = (fd_pubkey_t){ .ul = { 4000UL+i } }; uchar bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; test_bls_pubkey( bls, 10UL+i ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &vote[ i ], &node, 100UL*(i+1UL), 0U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &vote[ i ], &node, 100UL*(i+1UL), 0U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); } fd_vote_stakes_finalize( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2 ); for( ulong i=0UL; ivote, (fd_pubkey_t *)elem->identity, elem->stake, elem->commission, elem->identity_bls, elem->commission_block_bps, elem->pending_delegator_rewards, - (fd_pubkey_t const *)elem->commission_inflation, (fd_pubkey_t const *)elem->commission_block ); + (fd_pubkey_t const *)elem->commission_inflation, (fd_pubkey_t const *)elem->commission_block, &elem->location ); /* Reward recalculation resolves every epoch credits entry against the t_1 set, so only admitted accounts may get one. */ @@ -539,7 +539,7 @@ fd_ssload_recover_apply( fd_snapshot_manifest_t * manifest, fd_snapshot_manifest_vote_stakes_t const * elem = &manifest->epoch_stakes[t_2_idx].vote_stakes[i]; fd_vote_stakes_snap_insert_t_2( vote_stakes, vote_stakes_fork_id, (fd_pubkey_t *)elem->vote, (fd_pubkey_t *)elem->identity, elem->stake, elem->commission, elem->identity_bls, elem->commission_block_bps, elem->pending_delegator_rewards, - (fd_pubkey_t const *)elem->commission_inflation, (fd_pubkey_t const *)elem->commission_block ); + (fd_pubkey_t const *)elem->commission_inflation, (fd_pubkey_t const *)elem->commission_block, &elem->location ); } fd_vote_stakes_finalize( vote_stakes, vote_stakes_fork_id, FD_VOTE_STAKES_ITER_T_2 ); @@ -551,7 +551,7 @@ fd_ssload_recover_apply( fd_snapshot_manifest_t * manifest, for( ulong i=0UL; ivote_stakes_len; i++ ) { fd_snapshot_manifest_vote_stakes_t const * elem = &epoch_stakes->vote_stakes[i]; fd_vote_stakes_snap_insert_t_n( vote_stakes, vote_stakes_fork_id, n, (fd_pubkey_t *)elem->vote, (fd_pubkey_t *)elem->identity, elem->stake, elem->commission, elem->identity_bls, - elem->commission_block_bps, elem->pending_delegator_rewards, NULL, NULL ); + elem->commission_block_bps, elem->pending_delegator_rewards, NULL, NULL, &elem->location ); } fd_vote_stakes_finalize( vote_stakes, vote_stakes_fork_id, FD_VOTE_STAKES_ITER_T_2+(int)(n-2UL) ); } diff --git a/src/discof/restore/utils/fd_ssmanifest_parser.c b/src/discof/restore/utils/fd_ssmanifest_parser.c index 8850ae5af91..2180308d9cd 100644 --- a/src/discof/restore/utils/fd_ssmanifest_parser.c +++ b/src/discof/restore/utils/fd_ssmanifest_parser.c @@ -1696,6 +1696,7 @@ fd_ssmanifest_vote_stakes_from_v5( fd_snapshot_manifest_vote_stakes_t * out, memcpy( out->identity_bls, v5->bls_pubkey_compressed, FD_BLS_PUBKEY_COMPRESSED_SZ ); out->pending_delegator_rewards = v5->pending_delegator_rewards; out->has_identity_bls = 1; + out->location = v5->location; out->slot = v5->last_timestamp.slot; out->timestamp = v5->last_timestamp.timestamp; out->epoch_credits_history_len = v5->accumulated_lamports_len; @@ -1856,7 +1857,10 @@ state_process( fd_ssmanifest_parser_t * parser ) { } } - if( FD_UNLIKELY( parser->state==STATE_EPOCH_STAKES_VOTE_ACCOUNTS_VALUE_DATA_LENGTH ) ) parser->account_data_start = parser->off; + if( FD_UNLIKELY( parser->state==STATE_EPOCH_STAKES_VOTE_ACCOUNTS_VALUE_DATA_LENGTH ) ) { + parser->account_data_start = parser->off; + if( parser->epoch_idx!=ULONG_MAX ) manifest->epoch_stakes[ parser->epoch_idx ].vote_stakes[ parser->idx2 ].location = (fd_validator_location_t){ 0 }; + } if( FD_UNLIKELY( parser->state==STATE_EPOCH_STAKES_VOTE_ACCOUNTS_VALUE_DATA_V5 ) ) { if( FD_UNLIKELY( parser->epoch_idx!=ULONG_MAX && @@ -1903,7 +1907,10 @@ state_process( fd_ssmanifest_parser_t * parser ) { } } - if( FD_UNLIKELY( parser->state==STATE_VERSIONED_EPOCH_STAKES_STAKES_VOTE_ACCOUNTS_VALUE_DATA_LENGTH ) ) parser->account_data_start = parser->off; + if( FD_UNLIKELY( parser->state==STATE_VERSIONED_EPOCH_STAKES_STAKES_VOTE_ACCOUNTS_VALUE_DATA_LENGTH ) ) { + parser->account_data_start = parser->off; + if( parser->epoch_idx!=ULONG_MAX ) manifest->epoch_stakes[ parser->epoch_idx ].vote_stakes[ parser->idx2 ].location = (fd_validator_location_t){ 0 }; + } if( FD_UNLIKELY( parser->state==STATE_VERSIONED_EPOCH_STAKES_STAKES_VOTE_ACCOUNTS_VALUE_DATA_V5 ) ) { if( FD_UNLIKELY( parser->epoch_idx!=ULONG_MAX && diff --git a/src/discof/restore/utils/fd_ssmsg.h b/src/discof/restore/utils/fd_ssmsg.h index ccb88bad988..94d385dff74 100644 --- a/src/discof/restore/utils/fd_ssmsg.h +++ b/src/discof/restore/utils/fd_ssmsg.h @@ -3,6 +3,7 @@ #include "../../../flamenco/runtime/fd_runtime_const.h" #include "../../../flamenco/runtime/fd_blockhashes.h" +#include "../../../flamenco/runtime/program/vote/fd_validator_location.h" #define FD_SSMSG_MANIFEST_FULL (0) /* A snapshot manifest message from the full snapshot */ #define FD_SSMSG_MANIFEST_INCREMENTAL (1) /* A snapshot manifest message from the incremental snapshot */ @@ -83,6 +84,9 @@ struct fd_snapshot_manifest_vote_stakes { /* The validator BLS pubkey (used after SIMD-0326: Alpenglow) */ uchar identity_bls[ 48UL ]; + /* The validator's declared location (SIMD-0674) */ + fd_validator_location_t location; + /* The total amount of active stake for the vote account */ ulong stake; diff --git a/src/discof/restore/utils/test_ssload.c b/src/discof/restore/utils/test_ssload.c index 7f9f526245f..7a52016d160 100644 --- a/src/discof/restore/utils/test_ssload.c +++ b/src/discof/restore/utils/test_ssload.c @@ -839,8 +839,21 @@ test_recover_preserves_snapin_stake_delegations( fd_wksp_t * wksp, fd_snapshot_m manifest->epoch_stakes[1].vote_stakes[0].commission = 10; manifest->epoch_stakes[1].vote_stakes[0].has_identity_bls = 1; fd_memset( manifest->epoch_stakes[1].vote_stakes[0].identity_bls, 0xB2, sizeof(manifest->epoch_stakes[1].vote_stakes[0].identity_bls) ); + manifest->epoch_stakes[1].vote_stakes[0].location = (fd_validator_location_t){ 4051542, 618526, 4870645 }; manifest->epoch_stakes[1].total_stake = 5000UL; + uchar pubkey_t2[32]; fd_memset( pubkey_t2, 0xC0, 32 ); + uchar ident_t2[32]; fd_memset( ident_t2, 0xC1, 32 ); + manifest->epoch_stakes[0].vote_stakes_len = 1UL; + fd_memcpy( manifest->epoch_stakes[0].vote_stakes[0].vote, pubkey_t2, 32 ); + fd_memcpy( manifest->epoch_stakes[0].vote_stakes[0].identity, ident_t2, 32 ); + manifest->epoch_stakes[0].vote_stakes[0].stake = 3000UL; + manifest->epoch_stakes[0].vote_stakes[0].commission = 10; + manifest->epoch_stakes[0].vote_stakes[0].has_identity_bls = 1; + fd_memset( manifest->epoch_stakes[0].vote_stakes[0].identity_bls, 0xC2, sizeof(manifest->epoch_stakes[0].vote_stakes[0].identity_bls) ); + manifest->epoch_stakes[0].vote_stakes[0].location = (fd_validator_location_t){ -3955214, 3355452, 3699409 }; + manifest->epoch_stakes[0].total_stake = 3000UL; + /* First apply: simulate initial full snapshot load. */ FD_TEST( VALIDATE_MANIFEST( manifest )==0 ); FD_TEST( fd_ssload_recover_apply( manifest, bank, seed )==0 ); @@ -857,6 +870,9 @@ test_recover_preserves_snapin_stake_delegations( fd_wksp_t * wksp, fd_snapshot_m fd_vote_stakes_ele_t q[1]; FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, bank->vote_stakes_fork_id, (fd_pubkey_t *)pubkey_x, q ) ); FD_TEST( q->stake==5000UL ); + FD_TEST( q->location.x==4051542 && q->location.y==618526 && q->location.z==4870645 ); + FD_TEST( fd_vote_stakes_query_t_2( vote_stakes, bank->vote_stakes_fork_id, (fd_pubkey_t *)pubkey_t2, q ) ); + FD_TEST( q->location.x==-3955214 && q->location.y==3355452 && q->location.z==3699409 ); /* Manifest B: different vote stake (pubkey_Y). */ @@ -940,6 +956,7 @@ test_recover_preserves_snapin_stake_delegations( fd_wksp_t * wksp, fd_snapshot_m manifest->epoch_stakes[1].vote_stakes[0].has_identity_bls = 1; manifest->epoch_stakes[1].vote_stakes[0].commission_block_bps = 4321U; manifest->epoch_stakes[1].vote_stakes[0].pending_delegator_rewards = 99UL; + manifest->epoch_stakes[1].vote_stakes[0].location = (fd_validator_location_t){ -4646180, 2553206, -3534375 }; manifest->epoch_stakes[1].total_stake = 3000UL; FD_TEST( VALIDATE_MANIFEST( manifest )==0 ); @@ -952,6 +969,7 @@ test_recover_preserves_snapin_stake_delegations( fd_wksp_t * wksp, fd_snapshot_m FD_TEST( fd_vote_stakes_query_t_3( vote_stakes, bank->vote_stakes_fork_id, (fd_pubkey_t *)pubkey_z, q ) ); FD_TEST( fd_pubkey_eq( &q->node_account, (fd_pubkey_t *)ident_z ) ); FD_TEST( q->stake==3000UL && q->commission==17U ); + FD_TEST( q->location.x==-4646180 && q->location.y==2553206 && q->location.z==-3534375 ); FD_TEST( fd_vote_stakes_total_stake( vote_stakes, 2UL )==4000UL ); FD_TEST( fd_vote_stakes_total_stake( vote_stakes, 1UL )==3000UL ); FD_TEST( fd_vote_stakes_total_stake( vote_stakes, 0UL )==0UL ); diff --git a/src/discof/rpc/fuzz_rpc.c b/src/discof/rpc/fuzz_rpc.c index 1fccb9667a9..b6d10f17058 100644 --- a/src/discof/rpc/fuzz_rpc.c +++ b/src/discof/rpc/fuzz_rpc.c @@ -182,6 +182,7 @@ seed_mleaders( fd_rpc_tile_t * ctx, uchar epoch_msg_buf[ FD_EPOCH_INFO_MSG_HEADER_SZ + sizeof(fd_vote_stake_weight_t) ] __attribute__((aligned(alignof(fd_epoch_info_msg_t)))); fd_epoch_info_msg_t * emsg = (fd_epoch_info_msg_t *)fd_type_pun( epoch_msg_buf ); memset( emsg, 0, sizeof(epoch_msg_buf) ); + emsg->features.geo_aware_leader_schedule = FD_FEATURE_DISABLED; emsg->epoch = epoch; emsg->start_slot = start_slot; emsg->slot_cnt = epoch_len; diff --git a/src/discof/rpc/test_rpc_tile.c b/src/discof/rpc/test_rpc_tile.c index 14a95814f91..793a70f70e9 100644 --- a/src/discof/rpc/test_rpc_tile.c +++ b/src/discof/rpc/test_rpc_tile.c @@ -231,6 +231,7 @@ ingest_test_epoch( fd_multi_epoch_leaders_t * mleaders, uchar epoch_msg_buf[ FD_EPOCH_INFO_MSG_HEADER_SZ + sizeof(fd_vote_stake_weight_t) ] __attribute__((aligned(alignof(fd_epoch_info_msg_t)))); fd_epoch_info_msg_t * emsg = (fd_epoch_info_msg_t *)fd_type_pun( epoch_msg_buf ); memset( emsg, 0, sizeof(epoch_msg_buf) ); + emsg->features.geo_aware_leader_schedule = FD_FEATURE_DISABLED; emsg->epoch = epoch; emsg->start_slot = epoch*32UL; emsg->slot_cnt = 32UL; diff --git a/src/discof/tower/test_tower_tile.c b/src/discof/tower/test_tower_tile.c index 2539c10eedb..224e366fc56 100644 --- a/src/discof/tower/test_tower_tile.c +++ b/src/discof/tower/test_tower_tile.c @@ -848,7 +848,7 @@ test_fixture_replay( fd_wksp_t * wksp ) { ulong num_slots = 32UL; fd_vote_stake_weight_t fixture_stakes[1] = {{ .vote_key = {{0}}, .id_key = {{0}}, .stake = 1UL }}; - ctx->mleaders->lsched[0] = fd_epoch_leaders_join( fd_epoch_leaders_new( ctx->mleaders->_lsched[0], 0, start_slot - 1, num_slots + MOCK_SLOT_MAX + 100, 1UL, fixture_stakes ) ); + ctx->mleaders->lsched[0] = fd_epoch_leaders_join( fd_epoch_leaders_new( ctx->mleaders->_lsched[0], 0, start_slot - 1, num_slots + MOCK_SLOT_MAX + 100, 1UL, fixture_stakes, 0 ) ); ctx->mleaders->init_done[0] = 1; for( ulong slot = start_slot; slot < start_slot + num_slots; slot++ ) { @@ -959,7 +959,7 @@ eqvoc_setup( fd_wksp_t * wksp ) { fd_vote_stake_weight_t eqvoc_stakes[1] = {{ .vote_key = {{0}}, .id_key = {{0}}, .stake = 1UL }}; ulong eqvoc_slot_cnt = EQVOC_BOOT_CNT + MOCK_SLOT_MAX + 100; - ctx->mleaders->lsched[0] = fd_epoch_leaders_join( fd_epoch_leaders_new( ctx->mleaders->_lsched[0], 0, EQVOC_START_SLOT - 1, eqvoc_slot_cnt, 1UL, eqvoc_stakes ) ); + ctx->mleaders->lsched[0] = fd_epoch_leaders_join( fd_epoch_leaders_new( ctx->mleaders->_lsched[0], 0, EQVOC_START_SLOT - 1, eqvoc_slot_cnt, 1UL, eqvoc_stakes, 0 ) ); ctx->mleaders->init_done[0] = 1; for( ulong slot = EQVOC_START_SLOT; slot < EQVOC_START_SLOT + EQVOC_BOOT_CNT; slot++ ) { diff --git a/src/discof/votor/test_votor_tile.c b/src/discof/votor/test_votor_tile.c index f6207783339..49ec2122015 100644 --- a/src/discof/votor/test_votor_tile.c +++ b/src/discof/votor/test_votor_tile.c @@ -1312,6 +1312,7 @@ test_id_keyswitch( void ) { static uchar epoch_msg_mem[ FD_EPOCH_INFO_MSG_HEADER_SZ+sizeof(fd_vote_stake_weight_t) ] __attribute__((aligned(8))); fd_epoch_info_msg_t * epoch_msg = fd_type_pun( epoch_msg_mem ); *epoch_msg = (fd_epoch_info_msg_t){ .epoch = 1UL, .staked_vote_cnt = 1UL, .start_slot = 64UL, .slot_cnt = 64UL, .ns_per_slot = 400000000UL }; + epoch_msg->features.geo_aware_leader_schedule = FD_FEATURE_DISABLED; fd_vote_stake_weight_t * epoch_weight = fd_epoch_info_msg_stake_weights( epoch_msg ); *epoch_weight = (fd_vote_stake_weight_t){ .vote_key = new_id, .id_key = new_id, .stake = 10UL }; /* the new identity still leads every slot */ memcpy( epoch_weight->bls_key, epoch_info->validators[1].bls_key, sizeof(ag_bls_key_t) ); diff --git a/src/flamenco/features/fd_features_generated.c b/src/flamenco/features/fd_features_generated.c index 9e4f6264e7c..e00bf6dd9a3 100644 --- a/src/flamenco/features/fd_features_generated.c +++ b/src/flamenco/features/fd_features_generated.c @@ -2372,6 +2372,13 @@ fd_feature_id_t const ids[] = { .implemented = 1, .cleaned_up = 0 }, + { .index = offsetof(fd_features_t, geo_aware_leader_schedule)>>3, + .id = {"\x04\x02\x72\xc2\x34\x3e\x3f\x36\x27\xcf\x7c\xb9\x8b\x16\x9a\x66\x26\x24\x3d\x8c\xd6\x5b\x15\x99\x7a\x8b\x7c\x56\x58\x20\x00\x00"}, + /* GeoAwareLeaderSchedu1e111111111111111111111 */ + .name = "geo_aware_leader_schedule", + .implemented = 1, + .cleaned_up = 0 }, + { .index = ULONG_MAX } }; @@ -2691,6 +2698,7 @@ typedef struct fd_feature_id_lookup_entry fd_feature_id_lookup_entry_t; #define MAP_PERFECT_300 0x0aa7b29de47f96cbUL, .val = &ids[300] #define MAP_PERFECT_301 0xdbf2487401975107UL, .val = &ids[301] #define MAP_PERFECT_302 0xee06a6ef50308803UL, .val = &ids[302] +#define MAP_PERFECT_303 0x363f3e34c2720204UL, .val = &ids[303] #include "../../util/tmpl/fd_map_perfect.c" @@ -3004,4 +3012,5 @@ FD_STATIC_ASSERT( offsetof( fd_features_t, raise_cpi_nesting_limit_to_8 FD_STATIC_ASSERT( offsetof( fd_features_t, loader_v3_set_program_data_to_elf_length )>>3==300UL, layout ); FD_STATIC_ASSERT( offsetof( fd_features_t, validator_location_registration )>>3==301UL, layout ); FD_STATIC_ASSERT( offsetof( fd_features_t, enforce_validator_location_registration )>>3==302UL, layout ); +FD_STATIC_ASSERT( offsetof( fd_features_t, geo_aware_leader_schedule )>>3==303UL, layout ); FD_STATIC_ASSERT( sizeof( fd_features_t )>>3==FD_FEATURE_ID_CNT, layout ); diff --git a/src/flamenco/features/fd_features_generated.h b/src/flamenco/features/fd_features_generated.h index 50b44a1fbc6..9b8595e0ba2 100644 --- a/src/flamenco/features/fd_features_generated.h +++ b/src/flamenco/features/fd_features_generated.h @@ -8,10 +8,10 @@ #endif /* FEATURE_ID_CNT is the number of features in ids */ -#define FD_FEATURE_ID_CNT (303UL) +#define FD_FEATURE_ID_CNT (304UL) /* Feature set ID calculated from all feature names */ -#define FD_FEATURE_SET_ID (3342991122U) +#define FD_FEATURE_SET_ID (3069991853U) union fd_features { ulong f[ FD_FEATURE_ID_CNT ]; @@ -319,5 +319,6 @@ union fd_features { /* 0x0aa7b29de47f96cb */ ulong loader_v3_set_program_data_to_elf_length; /* 0xdbf2487401975107 */ ulong validator_location_registration; /* 0xee06a6ef50308803 */ ulong enforce_validator_location_registration; + /* 0x363f3e34c2720204 */ ulong geo_aware_leader_schedule; }; }; diff --git a/src/flamenco/features/feature_map.json b/src/flamenco/features/feature_map.json index 76bb282a654..347dc0127d9 100644 --- a/src/flamenco/features/feature_map.json +++ b/src/flamenco/features/feature_map.json @@ -301,5 +301,6 @@ {"name":"raise_cpi_nesting_limit_to_8","implemented":0,"pubkey":"6TkHkRmP7JZy1fdM6fg5uXn76wChQBWGokHBJzrLB3mj"}, {"name":"loader_v3_set_program_data_to_elf_length","implemented":1,"pubkey":"EhisBfVtGvEA8bVCVN5VMaYEaX6iTfoUrmcDi8LY7Kxy"}, {"name":"validator_location_registration","implemented":1,"pubkey":"Va1idatorLocat1onRegistrat1on11111111111111","comment":"SIMD-0674, placeholder pubkey"}, - {"name":"enforce_validator_location_registration","implemented":1,"pubkey":"EnforceVa1idatorLocat1onRegistrat1on1111111","comment":"placeholder pubkey"} + {"name":"enforce_validator_location_registration","implemented":1,"pubkey":"EnforceVa1idatorLocat1onRegistrat1on1111111","comment":"placeholder pubkey"}, + {"name":"geo_aware_leader_schedule","implemented":1,"pubkey":"GeoAwareLeaderSchedu1e111111111111111111111","comment":"placeholder pubkey"} ] diff --git a/src/flamenco/leaders/Local.mk b/src/flamenco/leaders/Local.mk index 6b911016f4e..b8959040d51 100644 --- a/src/flamenco/leaders/Local.mk +++ b/src/flamenco/leaders/Local.mk @@ -2,5 +2,11 @@ $(call add-hdrs,fd_leaders.h fd_multi_epoch_leaders.h) $(call add-objs,fd_leaders fd_multi_epoch_leaders,fd_flamenco) $(call make-unit-test,test_leaders,test_leaders,fd_flamenco fd_ballet fd_util) $(call make-unit-test,test_multi_leaders,test_multi_leaders,fd_flamenco fd_ballet fd_util) +$(call make-unit-test,test_leaders_geo,test_leaders_geo,fd_flamenco fd_ballet fd_util) $(call run-unit-test,test_leaders) $(call run-unit-test,test_multi_leaders) +$(call run-unit-test,test_leaders_geo) +ifdef FD_HAS_DOUBLE +$(call make-unit-test,test_leaders_geo_sim,test_leaders_geo_sim,fd_flamenco fd_ballet fd_util) +endif +$(call make-fuzz-test,fuzz_leaders_geo,fuzz_leaders_geo,fd_flamenco fd_ballet fd_util) diff --git a/src/flamenco/leaders/fd_leaders.c b/src/flamenco/leaders/fd_leaders.c index 7f58182dda0..0157d788b0b 100644 --- a/src/flamenco/leaders/fd_leaders.c +++ b/src/flamenco/leaders/fd_leaders.c @@ -1,6 +1,9 @@ #include "fd_leaders.h" +#include "../../ballet/base58/fd_base58.h" #include "../../ballet/chacha/fd_chacha_rng.h" +#include "../../ballet/sha256/fd_sha256.h" #include "../../ballet/wsample/fd_wsample.h" +#include "../runtime/sysvar/fd_sysvar_epoch_schedule.h" #define SORT_NAME sort_vote_weights_by_stake_id #define SORT_KEY_T fd_vote_stake_weight_t @@ -22,6 +25,14 @@ #define SORT_BEFORE(a,b) (memcmp( (a).key.uc, (b).key.uc, 32UL )>0) #include "../../util/tmpl/fd_sort.c" +/* FD_EPOCH_LEADERS_GEO_RUN_LENGTH is the maximum number of leaders in + a single cluster. Defined by SIMD-0675. */ +#define FD_EPOCH_LEADERS_GEO_RUN_LENGTH (3UL) + +/* FD_EPOCH_LEADERS_GEO_STAKE_FLOOR is the minimum % of the total stake + that each leaders radius must contain. Defined by SIMD-0675. */ +#define FD_EPOCH_LEADERS_GEO_STAKE_FLOOR (10UL) + ulong compute_id_weights_from_vote_weights( fd_stake_weight_t * stake_weight, fd_vote_stake_weight_t const * vote_stake_weight, @@ -74,13 +85,235 @@ fd_epoch_leaders_footprint( ulong pub_cnt, return FD_EPOCH_LEADERS_FOOTPRINT( pub_cnt, slot_cnt ); } +struct fd_geo_neighbor { + ulong dist2; + ulong stake; +}; +typedef struct fd_geo_neighbor fd_geo_neighbor_t; + +struct fd_geo_cluster { + uint members_cnt; + uint members[ FD_EPOCH_LEADERS_GEO_RUN_LENGTH ]; +}; +typedef struct fd_geo_cluster fd_geo_cluster_t; + +#define SORT_NAME sort_geo_neighbor +#define SORT_KEY_T fd_geo_neighbor_t +#define SORT_BEFORE(a,b) ((a).dist2<(b).dist2) +#include "../../util/tmpl/fd_sort.c" + +static void +fisher_yates_shuffle( fd_chacha_rng_t * rng, + uint * array, + ulong n ) { + for( ulong i=n; i>1UL; i-- ) { + ulong j = fd_chacha_rng_ulong_roll( rng, i ); + uint t = array[ i-1UL ]; + array[ i-1UL ] = array[ j ]; + array[ j ] = t; + } +} + +/* compute_smallest_radius returns the smallest squared distance r + from the validator v, such that the validators at most r away from + v make up at least FD_EPOCH_LEADERS_GEO_STAKE_FLOOR % of the total + stake. + + neighbor is an array of the pre-computed distances and stakes of + each validator from v. + + The algorithm is as such: + - Sort the neighbours by distance + - Sum the stake until we get to the stake floor */ +static ulong +compute_smallest_radius( fd_geo_neighbor_t * neighbor, + ulong cnt, + ulong total ) { + sort_geo_neighbor_inplace( neighbor, cnt ); + uint128 thresh = (uint128)total*FD_EPOCH_LEADERS_GEO_STAKE_FLOOR; + ulong acc = 0UL; + for( ulong i=0UL; i=thresh ) return neighbor[ i ].dist2; + } + FD_LOG_CRIT(( "unreachable" )); +} + +/* can_join_cluster returns 1 if the given validator leader can join + the given cluster. The leader can join if it is "close enough": + the squared distance between the leader and the cluster isn't + greater than the larger of the radii. I.e. the leader is within the + cluster's radius or the cluster is within the leader's radius. */ +static inline int +can_join_cluster( fd_vote_stake_weight_t const * stakes, + ulong const * radius, + ulong cluster, + ulong leader ) { + return fd_validator_location_dist2( + &stakes[ cluster ].location, &stakes[ leader ].location )<= + fd_ulong_max( radius[ cluster ], radius[ leader ] ); +} + +static void +assign_to_clusters( uint * sched, + ulong sched_cnt, + fd_vote_stake_weight_t const * stakes, + ulong const * radius, + fd_geo_cluster_t * clusters, + uint * open_clusters, + uint * eligible_clusters, + fd_chacha_rng_t * rng ) { + ulong open_clusters_cnt = 0UL; + ulong finalized_leaders_cnt = 0UL; + + /* Assign each leader in the schedule to a cluster, overwriting the + final leader schedule as we go. */ + for( ulong i=0UL; imembers[ cluster->members_cnt++ ] = leader; + + /* If the cluster is now full, shuffle its members and add it to + the finalized leader schedule. */ + if( cluster->members_cnt==FD_EPOCH_LEADERS_GEO_RUN_LENGTH ) { + fisher_yates_shuffle( rng, cluster->members, cluster->members_cnt ); + memcpy( sched+finalized_leaders_cnt, cluster->members, cluster->members_cnt*sizeof(uint) ); + finalized_leaders_cnt += cluster->members_cnt; + open_clusters_cnt--; + memmove( open_clusters+cluster_idx, open_clusters+cluster_idx+1UL, (open_clusters_cnt-cluster_idx)*sizeof(uint) ); + } + } + + /* Add any leftover open clusters to the final leader schedule, + shuffling them first. */ + fisher_yates_shuffle( rng, open_clusters, open_clusters_cnt ); + for( ulong j=0UL; jmembers, cluster->members_cnt ); + memcpy( sched+finalized_leaders_cnt, cluster->members, cluster->members_cnt*sizeof(uint) ); + finalized_leaders_cnt += cluster->members_cnt; + } +} + +/* geo_aware_reorder re-orders the given sched in-place, according to + SIMD-0675 Geo-Aware Leader Schedule. + - sched: the input leader schedule + - sched_cnt: the number of validators in the leader schedule + - stakes: metadata about each vote account, including their stake + weight and validator location + - stakes_cnt: the number of entries in the stakes + - epoch: the epoch this leader schedule is computed for + - scratch: scratch memory region used for intermediate computations */ +static void +geo_aware_reorder( uint * sched, + ulong sched_cnt, + fd_vote_stake_weight_t const * stakes, + ulong stakes_cnt, + ulong epoch, + void * scratch ) { + FD_SCRATCH_ALLOC_INIT( l, scratch ); + /* radius[v] will hold the smallest radius for each validator v which + contains FD_EPOCH_LEADERS_GEO_STAKE_FLOOR % of the total stake. */ + ulong * radius = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), stakes_cnt*sizeof(ulong) ); + /* neighbor[ u ] will hold the squared distance from the validator + whose smallest radius is being computed to validator u, and u's + stake. The same memory is then reused in assign_to_clusters, + so we size it for the larger of the two uses. */ + fd_geo_neighbor_t * neighbor = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_geo_neighbor_t), + stakes_cnt*fd_ulong_max( sizeof(fd_geo_neighbor_t), sizeof(fd_geo_cluster_t) ) ); + /* open_clusters and eligible_clusters are used in assign_to_clusters, + we just reserve the memory here. */ + uint * open_clusters = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), stakes_cnt*sizeof(uint) ); + uint * eligible_clusters = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), stakes_cnt*sizeof(uint) ); + FD_SCRATCH_ALLOC_FINI( l, 1UL ); + + /* Sum up the total stake and sanity-check that all the validators + have a valid location set. As geo_aware_leader_schedule can only + be activated after enforce_validator_location_registration, all + validators should have a valid location set. */ + ulong total = 0UL; + for( ulong i=0UL; igeo_aware_leader_schedule; + if( activation_slot==FD_FEATURE_DISABLED ) return 0; + return schedule_epoch>fd_slot_to_epoch( epoch_schedule, activation_slot, NULL ); +} + void * fd_epoch_leaders_new( void * shmem, ulong epoch, ulong slot0, ulong slot_cnt, ulong pub_cnt, - fd_vote_stake_weight_t * stakes ) { + fd_vote_stake_weight_t * stakes, + int geo_aware ) { if( FD_UNLIKELY( !shmem ) ) { FD_LOG_WARNING(( "NULL shmem" )); return NULL; @@ -102,22 +335,25 @@ fd_epoch_leaders_new( void * shmem, } /* The eventual layout that we want is: - struct (align=8, footprint=48) + struct (align=8, footprint=64) list of indices (align=4, footprint=4*ceil(slot_cnt/4)) + (up to 28 bytes of padding to align to 32) vote addr per rotation (align=32, footprint=32*ceil(slot_cnt/4)) - (up to 60 bytes of padding to align to 64) + (up to 32 bytes of padding to align to 64) list of pubkeys (align=32, footprint=32*pub_cnt) (possibly 32 bytes of padding to align to 64) - but in order to generate the list of indices, we want to use - wsample, which needs some memory to work. Turns out that we - probably have all the memory we need right here in shmem, we just - need to be careful about how we use it; for most of the values of - pub_cnt we care about, wsample's footprint is less than 32*pub_cnt. + The memory for the list of pubkeys is used three times, in this + order: + 1. by wsample, to generate the list of indices. The region is + sized for the larger of 32*pub_cnt and wsample's footprint. + 2. by the geo-aware re-ordering, as scratch space: at most 32 + bytes per pubkey. + 3. for the pubkeys themselves, which we only copy in once both + are done. + + There's a lot of type punning going on here, so watch out. */ - This works out because we can delay copying the pubkeys until we're - done with the wsample object. There's a lot of type punning going - on here, so watch out. */ ulong sched_cnt = (slot_cnt+FD_EPOCH_SLOTS_PER_ROTATION-1UL)/FD_EPOCH_SLOTS_PER_ROTATION; fd_epoch_leaders_t * leaders = (fd_epoch_leaders_t *)fd_type_pun( (void *)laddr ); @@ -137,6 +373,9 @@ fd_epoch_leaders_new( void * shmem, void * wsample_mem = (void *)fd_type_pun( (void *)laddr ); fd_pubkey_t * pubkeys = (fd_pubkey_t *)fd_type_pun( (void *)laddr ); + FD_STATIC_ASSERT( sizeof(fd_geo_neighbor_t)<=sizeof(fd_geo_cluster_t), geo_scratch ); + FD_STATIC_ASSERT( sizeof(ulong)+sizeof(fd_geo_cluster_t)+2UL*sizeof(uint)<=sizeof(fd_pubkey_t), geo_scratch ); + FD_TEST( laddr+fd_wsample_footprint( pub_cnt, 0 )<=(ulong)wsample_mem + fd_epoch_leaders_footprint( pub_cnt, slot_cnt ) ); /* Create and seed ChaCha20Rng */ @@ -158,6 +397,11 @@ fd_epoch_leaders_new( void * shmem, fd_wsample_delete( fd_wsample_leave( wsample ) ); fd_chacha_rng_delete( fd_chacha_rng_leave( rng ) ); + /* Apply the geo-aware re-ordering */ + if( geo_aware ) { + geo_aware_reorder( sched, sched_cnt, stakes, pub_cnt, epoch, wsample_mem ); + } + /* Now we can use the space for the pubkeys */ for( ulong i=0UL; ilsched[i] = fd_epoch_leaders_join( fd_epoch_leaders_new( leaders->_lsched[i], i, 0UL, 1UL, 1UL, dummy_stakes ) ); + leaders->lsched[i] = fd_epoch_leaders_join( fd_epoch_leaders_new( leaders->_lsched[i], i, 0UL, 1UL, 1UL, dummy_stakes, 0 ) ); FD_TEST( leaders->lsched[i] ); leaders->init_done[i] = 0; } @@ -106,6 +106,7 @@ fd_multi_epoch_leaders_stake_msg_init( fd_multi_epoch_leaders_t * mleaders, mleaders->scratch->start_slot = msg->start_slot; mleaders->scratch->slot_cnt = msg->slot_cnt; mleaders->scratch->staked_cnt = msg->staked_vote_cnt; + mleaders->scratch->geo_aware_lsched = 0; fd_memcpy( mleaders->vote_stake_weight, fd_stake_weight_msg_stake_weights( msg ), msg->staked_vote_cnt*sizeof(fd_vote_stake_weight_t) ); } @@ -124,6 +125,7 @@ fd_multi_epoch_leaders_epoch_msg_init( fd_multi_epoch_leaders_t * mleaders, mleaders->scratch->start_slot = msg->start_slot; mleaders->scratch->slot_cnt = msg->slot_cnt; mleaders->scratch->staked_cnt = msg->staked_vote_cnt; + mleaders->scratch->geo_aware_lsched = fd_epoch_leaders_use_geo_aware( &msg->features, &msg->epoch_schedule, msg->epoch ); fd_vote_stake_weight_t const * weights = fd_epoch_info_msg_stake_weights( msg ); fd_memcpy( mleaders->vote_stake_weight, weights, msg->staked_vote_cnt*sizeof(fd_vote_stake_weight_t) ); @@ -135,6 +137,7 @@ fd_multi_epoch_leaders_stake_msg_fini( fd_multi_epoch_leaders_t * mleaders ) { const ulong slot0 = mleaders->scratch->start_slot; const ulong slot_cnt = mleaders->scratch->slot_cnt; const ulong pub_cnt = mleaders->scratch->staked_cnt; + const int geo_aware = mleaders->scratch->geo_aware_lsched; const ulong epoch_idx = epoch % MULTI_EPOCH_LEADERS_EPOCH_CNT; fd_vote_stake_weight_t * stakes = mleaders->vote_stake_weight; @@ -153,7 +156,7 @@ fd_multi_epoch_leaders_stake_msg_fini( fd_multi_epoch_leaders_t * mleaders ) { uchar * lsched_mem = mleaders->_lsched[epoch_idx]; mleaders->lsched[epoch_idx] = fd_epoch_leaders_join( fd_epoch_leaders_new( lsched_mem, epoch, slot0, slot_cnt, - pub_cnt, stakes ) ); + pub_cnt, stakes, geo_aware ) ); mleaders->init_done[epoch_idx] = 1; } diff --git a/src/flamenco/leaders/fd_multi_epoch_leaders.h b/src/flamenco/leaders/fd_multi_epoch_leaders.h index 09a5db0bae2..1e08e32b6cb 100644 --- a/src/flamenco/leaders/fd_multi_epoch_leaders.h +++ b/src/flamenco/leaders/fd_multi_epoch_leaders.h @@ -28,6 +28,7 @@ struct fd_multi_epoch_leaders_priv { ulong start_slot; ulong slot_cnt; ulong staked_cnt; + int geo_aware_lsched; } scratch[1]; _lsched_t _lsched[MULTI_EPOCH_LEADERS_EPOCH_CNT]; diff --git a/src/flamenco/leaders/fuzz_leaders_geo.c b/src/flamenco/leaders/fuzz_leaders_geo.c new file mode 100644 index 00000000000..b2242a9b7e1 --- /dev/null +++ b/src/flamenco/leaders/fuzz_leaders_geo.c @@ -0,0 +1,58 @@ +#include "fd_leaders.c" +#include +#include + +static fd_validator_location_t const CITY[ 8 ] = { + { 4051542, 618526, 4870645 }, { 3977778, 0, 4969055 }, + { 1333729, -4654333, 4138065 }, { -3955214, 3355452, 3699409 }, + { -4646017, 2553114, -3534483 }, { 4017263, -4252595, -2532673 }, + { -1523141, 6191788, 149262 }, { 1074501, -4842760, 3995767 } +}; + +#define PUB_MAX (64UL) +#define SCHED_MAX (1024UL) + +static fd_pubkey_t scratch[ PUB_MAX ]; + +int +LLVMFuzzerInitialize( int * argc, + char *** argv ) { + putenv( "FD_LOG_BACKTRACE=0" ); + setenv( "FD_LOG_PATH", "", 0 ); + fd_boot( argc, argv ); + fd_log_level_stderr_set( 4 ); + atexit( fd_halt ); + return 0; +} + +int +LLVMFuzzerTestOneInput( uchar const * data, + ulong size ) { + if( size<10UL ) return 0; + ulong pub_cnt = 1UL+( (ulong)data[ 0 ]%PUB_MAX ); + ulong epoch = FD_LOAD( ulong, data+1UL ); + data += 9UL; size -= 9UL; + if( size>3 ); + } + data += pub_cnt*3UL; size -= pub_cnt*3UL; + + ulong sched_cnt = fd_ulong_min( size, SCHED_MAX ); + if( !sched_cnt ) return 0; + static uint sched[ SCHED_MAX ]; + static uint cnt [ PUB_MAX ]; + memset( cnt, 0, sizeof(cnt) ); + for( ulong i=0UL; i +#include + +#define REF_PUB_MAX (64UL) +#define REF_SCHED_MAX (512UL) + +static fd_validator_location_t const CITY[ 8 ] = { + { 4051542, 618526, 4870645 }, { 3977778, 0, 4969055 }, + { 1333729, -4654333, 4138065 }, { -3955214, 3355452, 3699409 }, + { -4646017, 2553114, -3534483 }, { 4017263, -4252595, -2532673 }, + { -1523141, 6191788, 149262 }, { 1074501, -4842760, 3995767 } +}; + +static fd_validator_location_t +city_loc( ulong i ) { + fd_validator_location_t loc = CITY[ i%8UL ]; + loc.x += (int)( 7UL*( (i/8UL)%41UL ) ); + return loc; +} + +static void +ref_shuffle( fd_chacha_rng_t * rng, + uint * a, + ulong n ) { + if( n<2UL ) return; + for( ulong i=n-1UL; i>=1UL; i-- ) { + ulong j = fd_chacha_rng_ulong_roll( rng, i+1UL ); + uint t = a[ i ]; a[ i ] = a[ j ]; a[ j ] = t; + } +} + +static ulong +ref_radius( fd_vote_stake_weight_t const * stakes, + ulong pub_cnt, + ulong v, + ulong total ) { + static ulong d[ REF_PUB_MAX ]; + static ulong w[ REF_PUB_MAX ]; + ulong n = 0UL; + for( ulong u=0UL; u0UL && d[ k-1UL ]>d[ k ]; k-- ) { + ulong td = d[ k ]; d[ k ] = d[ k-1UL ]; d[ k-1UL ] = td; + ulong tw = w[ k ]; w[ k ] = w[ k-1UL ]; w[ k-1UL ] = tw; + } + } + uint128 acc = 0; + for( ulong i=0UL; i=(uint128)total ) return d[ i ]; + } + FD_LOG_ERR(( "unreachable" )); +} + +static void +ref_reorder( uint * sched, + ulong sched_cnt, + fd_vote_stake_weight_t const * stakes, + ulong pub_cnt, + ulong epoch ) { + static ulong r [ REF_PUB_MAX ]; + static uint anchor [ REF_PUB_MAX ]; + static uint cnt [ REF_PUB_MAX ]; + static uint mem [ REF_PUB_MAX ][ FD_EPOCH_LEADERS_GEO_RUN_LENGTH ]; + static uint near_idx[ REF_PUB_MAX ]; + static uint order [ REF_PUB_MAX ]; + static uint out [ REF_SCHED_MAX ]; + + ulong total = 0UL; + for( ulong i=0UL; i>(8UL*b) ); + uchar key[ 32 ]; + fd_sha256_hash( buf, sizeof(buf), key ); + fd_chacha_rng_t _rng[1]; + fd_chacha_rng_t * rng = fd_chacha_rng_join( fd_chacha_rng_new( _rng, FD_CHACHA_RNG_MODE_MOD ) ); + fd_chacha_rng_init( rng, key, FD_CHACHA_RNG_ALGO_CHACHA20 ); + + ulong open = 0UL; + ulong n = 0UL; + for( ulong i=0UL; i=(uint128)total ) { ref = s[ i ].dist2; break; } } + FD_TEST( compute_smallest_radius( p, n, total )==ref ); + } +} + +static void +test_radius_fallback( void ) { + static fd_geo_neighbor_t p [ 64 ]; + static ulong slot[ 64 ]; + ulong const n = 64UL; + ulong slot_cnt = n; + ulong rank = 1UL; + for( ulong i=0UL; i2UL ) { + ulong mid = slot_cnt/2UL; + p[ slot[ 0 ] ].dist2 = rank++; + p[ slot[ mid ] ].dist2 = rank++; + memmove( slot+mid, slot+mid+1UL, (slot_cnt-mid-1UL)*sizeof(ulong) ); + memmove( slot, slot+1UL, (slot_cnt-2UL)*sizeof(ulong) ); + slot_cnt -= 2UL; + } + for( ulong i=0UL; i=0 ); + if( !pid ) { + fd_log_level_logfile_set( 6 ); + fd_log_level_stderr_set( 6 ); + uint s[ 4 ] = { 0U, 1U, 0U, 1U }; + geo_aware_reorder( s, 4UL, stakes, pub_cnt, 1UL, scratch ); + _exit( 0 ); + } + int status = 0; + FD_TEST( waitpid( pid, &status, 0 )==pid ); + FD_TEST( WIFEXITED( status ) ); + return WEXITSTATUS( status ); +} + +static void +test_unlocated_err( void ) { + static fd_vote_stake_weight_t stakes[ 3 ]; + memset( stakes, 0, sizeof(stakes) ); + for( ulong i=0UL; i<3UL; i++ ) { stakes[ i ].stake = 10UL; stakes[ i ].location = city_loc( i ); } + FD_TEST( reorder_exit_status( stakes, 3UL )==0 ); + stakes[ 1 ].location = (fd_validator_location_t){ 6000000, 0, 0 }; + FD_TEST( reorder_exit_status( stakes, 3UL )==1 ); + stakes[ 1 ].location = city_loc( 1UL ); + stakes[ 2 ].location = (fd_validator_location_t){ 6000000, 0, 0 }; + FD_TEST( reorder_exit_status( stakes, 3UL )==1 ); +} + +FD_IMPORT_BINARY( e454_stakes, "src/flamenco/leaders/fixtures/epoch-stakes-454.bin" ); + +#define SLOT_CNT (432000UL) +#define PUB_MAX (2048UL) + +static uchar base_buf[ FD_EPOCH_LEADERS_FOOTPRINT( PUB_MAX, SLOT_CNT ) ] __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))); +static uchar geo_buf [ FD_EPOCH_LEADERS_FOOTPRINT( PUB_MAX, SLOT_CNT ) ] __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))); +static fd_vote_stake_weight_t lstakes[ PUB_MAX ]; + +static fd_epoch_leaders_t * +lsched( uchar * buf, + ulong epoch, + ulong slot_cnt, + ulong pub_cnt, + int geo ) { + fd_epoch_leaders_t * leaders = fd_epoch_leaders_join( fd_epoch_leaders_new( buf, epoch, 0UL, slot_cnt, pub_cnt, lstakes, geo ) ); + FD_TEST( leaders ); + return leaders; +} + +static void +fill_stakes( ulong pub_cnt ) { + FD_TEST( pub_cnt<=PUB_MAX && pub_cnt<=e454_stakes_sz/sizeof(fd_stake_weight_t) ); + fd_stake_weight_t const * e454 = (fd_stake_weight_t const *)e454_stakes; + memset( lstakes, 0, sizeof(lstakes) ); + for( ulong i=0UL; isched_cnt==geo->sched_cnt && base->pub_cnt==geo->pub_cnt ); + memset( cnt, 0, sizeof(cnt) ); + for( ulong i=0UL; isched_cnt; i++ ) cnt[ base->sched[ i ] ]++; + for( ulong i=0UL; isched_cnt; i++ ) { FD_TEST( cnt[ geo->sched[ i ] ] ); cnt[ geo->sched[ i ] ]--; } + for( ulong i=0UL; ipub_cnt; i++ ) FD_TEST( !cnt[ i ] ); + for( ulong i=0UL; isched_cnt; i++ ) FD_TEST( !memcmp( geo->vote_addr+i, &lstakes[ geo->sched[ i ] ].vote_key, 32UL ) ); +} + +static void +test_single_location( void ) { + ulong const R = FD_EPOCH_LEADERS_GEO_RUN_LENGTH; + fill_stakes( 500UL ); + for( ulong i=0UL; i<500UL; i++ ) lstakes[ i ].location = CITY[ 0 ]; + fd_epoch_leaders_t * base = lsched( base_buf, 9UL, SLOT_CNT, 500UL, 0 ); + fd_epoch_leaders_t * geo = lsched( geo_buf, 9UL, SLOT_CNT, 500UL, 1 ); + check_permutation( base, geo ); + int differ = 0; + for( ulong i=0UL; isched_cnt; i+=R ) { + ulong n = fd_ulong_min( R, base->sched_cnt-i ); + uint b[ FD_EPOCH_LEADERS_GEO_RUN_LENGTH ]; + memcpy( b, base->sched+i, n*sizeof(uint) ); + for( ulong j=0UL; jsched[ i+j ] ) break; + FD_TEST( ksched+i, geo->sched+i, n*sizeof(uint) ); + } + FD_TEST( differ ); +} + +static void +test_two_clusters( void ) { + ulong const R = FD_EPOCH_LEADERS_GEO_RUN_LENGTH; + fill_stakes( 200UL ); + for( ulong i=0UL; i<200UL; i++ ) lstakes[ i ].location = (i&1UL) ? CITY[ 3 ] : CITY[ 0 ]; + fd_epoch_leaders_t * base = lsched( base_buf, 11UL, SLOT_CNT, 200UL, 0 ); + fd_epoch_leaders_t * geo = lsched( geo_buf, 11UL, SLOT_CNT, 200UL, 1 ); + check_permutation( base, geo ); + ulong full = (geo->sched_cnt-2UL*(R-1UL))/R; + for( ulong k=0UL; ksched[ k*R ]&1U; + for( ulong j=1UL; jsched[ k*R+j ]&1U)==c ); + } +} + +static void +test_determinism( void ) { + fill_stakes( 300UL ); + fd_epoch_leaders_t * a = lsched( base_buf, 13UL, SLOT_CNT, 300UL, 1 ); + fd_epoch_leaders_t * b = lsched( geo_buf, 13UL, SLOT_CNT, 300UL, 1 ); + FD_TEST( !memcmp( a->sched, b->sched, a->sched_cnt*sizeof(uint) ) ); + b = lsched( geo_buf, 14UL, SLOT_CNT, 300UL, 1 ); + FD_TEST( memcmp( a->sched, b->sched, a->sched_cnt*sizeof(uint) ) ); + + for( ulong slot_cnt=1UL; slot_cnt<=64UL; slot_cnt++ ) { + for( ulong pub_cnt=1UL; pub_cnt<=5UL; pub_cnt++ ) { + fd_epoch_leaders_t * base = lsched( base_buf, slot_cnt, slot_cnt, pub_cnt, 0 ); + fd_epoch_leaders_t * geo = lsched( geo_buf, slot_cnt, slot_cnt, pub_cnt, 1 ); + check_permutation( base, geo ); + } + } +} + +static void +test_golden( void ) { + static uchar const golden[ 32 ] = { + 0x7d, 0xe3, 0x0e, 0x78, 0x66, 0x7f, 0x8b, 0x79, + 0x5b, 0xcc, 0x0f, 0x5c, 0x0b, 0x34, 0x9c, 0x33, + 0x28, 0x21, 0x56, 0x31, 0x05, 0x8b, 0x2c, 0xd7, + 0x6a, 0x1e, 0x5f, 0xf6, 0xe3, 0x12, 0xb3, 0x51 + }; + fill_stakes( 300UL ); + fd_epoch_leaders_t * base = lsched( base_buf, 13UL, SLOT_CNT, 300UL, 0 ); + fd_epoch_leaders_t * geo = lsched( geo_buf, 13UL, SLOT_CNT, 300UL, 1 ); + uchar hash[ 32 ]; + fd_sha256_hash( geo->sched, geo->sched_cnt*sizeof(uint), hash ); + FD_LOG_NOTICE(( "geo golden " FD_LOG_HEX16_FMT " " FD_LOG_HEX16_FMT, FD_LOG_HEX16_FMT_ARGS( hash ), FD_LOG_HEX16_FMT_ARGS( hash+16 ) )); + FD_TEST( memcmp( base->sched, geo->sched, geo->sched_cnt*sizeof(uint) ) ); + FD_TEST( !memcmp( hash, golden, 32UL ) ); +} + +int +main( int argc, + char ** argv ) { + fd_boot( &argc, &argv ); + fd_rng_t _rng[1]; + fd_rng_t * rng = fd_rng_join( fd_rng_new( _rng, 675U, 0UL ) ); + + test_city_locations_valid(); + test_radius( rng ); + test_radius_fallback(); + test_differential( rng ); + test_unlocated_err(); + test_single_location(); + test_two_clusters(); + test_determinism(); + test_golden(); + + fd_rng_delete( fd_rng_leave( rng ) ); + FD_LOG_NOTICE(( "pass" )); + fd_halt(); + return 0; +} diff --git a/src/flamenco/leaders/test_leaders_geo_sim.c b/src/flamenco/leaders/test_leaders_geo_sim.c new file mode 100644 index 00000000000..ee3e3c664ed --- /dev/null +++ b/src/flamenco/leaders/test_leaders_geo_sim.c @@ -0,0 +1,127 @@ +#include "fd_leaders.h" +#include + +FD_IMPORT_BINARY( e454_stakes, "src/flamenco/leaders/fixtures/epoch-stakes-454.bin" ); + +#define SLOT_CNT (432000UL) +#define PUB_MAX (2048UL) +#define PI (3.14159265358979323846) + +static uchar base_buf[ FD_EPOCH_LEADERS_FOOTPRINT( PUB_MAX, SLOT_CNT ) ] __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))); +static uchar geo_buf [ FD_EPOCH_LEADERS_FOOTPRINT( PUB_MAX, SLOT_CNT ) ] __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))); +static fd_vote_stake_weight_t stakes[ PUB_MAX ]; + +static fd_validator_location_t +from_latlon( double lat, + double lon ) { + double a = 6378137.0; + double e2 = 6.69437999014e-3; + double phi = lat*PI/180.0; + double lam = lon*PI/180.0; + double n = a/sqrt( 1.0-e2*sin( phi )*sin( phi ) ); + return (fd_validator_location_t){ .x = (int)lround( n*cos( phi )*cos( lam ) ), + .y = (int)lround( n*cos( phi )*sin( lam ) ), + .z = (int)lround( n*(1.0-e2)*sin( phi ) ) }; +} + +struct city { double lat, lon; uint weight; }; + +static struct city const cities[] = { + { 50.11, 8.68, 22 }, { 52.37, 4.90, 12 }, { 51.51, -0.13, 7 }, { 53.35, -6.26, 2 }, + { 48.86, 2.35, 2 }, { 52.23, 21.01, 2 }, { 47.38, 8.54, 2 }, { 59.33, 18.07, 1 }, + { 54.69, 25.28, 2 }, { 39.04, -77.49, 9 }, { 40.71, -74.01, 6 }, { 41.88, -87.63, 3 }, + { 32.78, -96.80, 2 }, { 34.05,-118.24, 4 }, { 40.76,-111.89, 2 }, { 43.65, -79.38, 2 }, + {-23.55, -46.63, 1 }, { 35.68, 139.69, 9 }, { 1.35, 103.82, 4 }, { 22.32, 114.17, 1 }, + { 37.57, 126.98, 1 } +}; +#define CITY_CNT (sizeof(cities)/sizeof(cities[0])) + +static double +handover_ms( fd_validator_location_t const * u, + fd_validator_location_t const * v ) { + double chord = sqrt( (double)fd_validator_location_dist2( u, v ) ); + double km = 2.0*6371.0*asin( fmin( 1.0, chord/(2.0*6371000.0) ) ); + return km/150.0; +} + +static void +report( char const * label, + fd_epoch_leaders_t const * l, + ulong adv0 ) { + double sum = 0.0; + double sum_hon = 0.0; + ulong hon_cnt = 0UL; + ulong run = 0UL; + ulong run_max = 0UL; + for( ulong i=0UL; isched_cnt; i++ ) { + uint cur = l->sched[ i ]; + if( i ) { + uint prev = l->sched[ i-1UL ]; + double ms = handover_ms( &stakes[ prev ].location, &stakes[ cur ].location ); + sum += ms; + if( prev=adv0 ? run+1UL : 0UL; + run_max = fd_ulong_max( run_max, run ); + } + FD_LOG_NOTICE(( "%-5s mean handover %5.1f ms (honest %5.1f ms) adversary max run %lu", + label, sum/(double)(l->sched_cnt-1UL), sum_hon/(double)hon_cnt, run_max )); +} + +int +main( int argc, + char ** argv ) { + fd_boot( &argc, &argv ); + fd_rng_t _rng[1]; + fd_rng_t * rng = fd_rng_join( fd_rng_new( _rng, 1234U, 0UL ) ); + + ulong const hon_cnt = 2000UL; + ulong const adv_cnt = 10UL; + fd_stake_weight_t const * e454 = (fd_stake_weight_t const *)e454_stakes; + FD_TEST( hon_cnt<=e454_stakes_sz/sizeof(fd_stake_weight_t) ); + uint weight_sum = 0U; + for( ulong c=0UL; c=cities[ c ].weight ) pick -= cities[ c++ ].weight; + stakes[ i ].location = from_latlon( cities[ c ].lat+0.4*fd_rng_double_c( rng )-0.2, cities[ c ].lon+0.4*fd_rng_double_c( rng )-0.2 ); + total += stakes[ i ].stake; + } + for( ulong i=hon_cnt; if.features, &bank->f.epoch_schedule, epoch ) ) ); if( FD_UNLIKELY( !leaders ) ) { FD_LOG_ERR(( "Unable to init and join fd_epoch_leaders" )); } @@ -122,7 +123,8 @@ fd_runtime_update_leaders( fd_bank_t * bank, slot0, slot_cnt, stake_weight_cnt, - epoch_weights ) ); + epoch_weights, + fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, epoch ) ) ); if( FD_UNLIKELY( !leaders ) ) { FD_LOG_ERR(( "Unable to init and join fd_epoch_leaders" )); } @@ -1723,7 +1725,7 @@ fd_runtime_init_bank_from_genesis( fd_banks_t * banks, fd_vote_stakes_ele_t ele[1]; fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter, ele ); fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &ele->pubkey, &ele->node_account, ele->stake, ele->commission, ele->bls_key, - ele->block_revenue_commission_bps, ele->pending_delegator_rewards, &ele->inflation_collector, &ele->block_collector ); + ele->block_revenue_commission_bps, ele->pending_delegator_rewards, &ele->inflation_collector, &ele->block_collector, &ele->location ); } fd_vote_stakes_finalize( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2 ); fd_vote_stakes_refresh( vote_stakes, fork_id, accdb, bank->accdb_fork_id ); diff --git a/src/flamenco/runtime/program/vote/fd_validator_location.h b/src/flamenco/runtime/program/vote/fd_validator_location.h index eafa22bb3d4..ee31b91d42a 100644 --- a/src/flamenco/runtime/program/vote/fd_validator_location.h +++ b/src/flamenco/runtime/program/vote/fd_validator_location.h @@ -159,6 +159,15 @@ fd_validator_location_is_valid( fd_validator_location_t const * loc ) { dist2<=FD_VALIDATOR_LOCATION_SURFACE_DIST2+FD_VALIDATOR_LOCATION_SURFACE_TOLERANCE; } +FD_FN_PURE static inline ulong +fd_validator_location_dist2( fd_validator_location_t const * a, + fd_validator_location_t const * b ) { + long dx = (long)a->x - (long)b->x; + long dy = (long)a->y - (long)b->y; + long dz = (long)a->z - (long)b->z; + return (ulong)( dx*dx ) + (ulong)( dy*dy ) + (ulong)( dz*dz ); +} + FD_PROTOTYPES_END #endif /* HEADER_fd_src_flamenco_runtime_program_vote_fd_validator_location_h */ diff --git a/src/flamenco/runtime/program/vote/fd_vote_codec.c b/src/flamenco/runtime/program/vote/fd_vote_codec.c index 2d36a1f224a..fe3cd14bbb2 100644 --- a/src/flamenco/runtime/program/vote/fd_vote_codec.c +++ b/src/flamenco/runtime/program/vote/fd_vote_codec.c @@ -884,6 +884,13 @@ fd_vote_account_is_valid_v5( uchar const * data, fd_mem_iszero( data+FD_VOTE_STATE_V5_PAD_OFF, FD_VOTE_STATE_V5_PAD_SZ ); } +fd_validator_location_t +fd_vote_account_location( uchar const * data, + ulong data_sz ) { + if( !fd_vote_account_is_valid_v5( data, data_sz ) ) return (fd_validator_location_t){0}; + return FD_LOAD( fd_validator_location_t, data+V5_OFF( location ) ); +} + fd_vote_state_versioned_t * fd_vote_state_versioned_new( fd_vote_state_versioned_t * self, uint kind ) { diff --git a/src/flamenco/runtime/program/vote/fd_vote_codec.h b/src/flamenco/runtime/program/vote/fd_vote_codec.h index c05c2283c07..c3d9d4b15f3 100644 --- a/src/flamenco/runtime/program/vote/fd_vote_codec.h +++ b/src/flamenco/runtime/program/vote/fd_vote_codec.h @@ -794,6 +794,10 @@ fd_vote_account_v5( uchar * data ) { return fd_type_pun( data+sizeof(uint) ); } +fd_validator_location_t +fd_vote_account_location( uchar const * data, + ulong data_sz ); + static inline void fd_vote_state_v5_commission_get( fd_vote_state_v5_t const * self, uint kind, diff --git a/src/flamenco/runtime/program/vote/test_validator_location.c b/src/flamenco/runtime/program/vote/test_validator_location.c index 20b1ef36714..347a45a4b5d 100644 --- a/src/flamenco/runtime/program/vote/test_validator_location.c +++ b/src/flamenco/runtime/program/vote/test_validator_location.c @@ -52,6 +52,22 @@ test_edges( void ) { for( ulong i=0UL; i #include #include +#include #define SENTINEL ((fd_accdb_fork_id_t){ .val = USHORT_MAX }) static fd_validator_location_t const LOC_FRA = { 4051542, 618526, 4870645 }; +static fd_validator_location_t const no_loc = {0}; static fd_validator_location_t const * voter0_location = &LOC_FRA; #define TEST_SLOTS_PER_EPOCH (12UL) @@ -431,8 +433,8 @@ test_env_create( test_env_t * env, fd_wksp_t * wksp ) { account data. */ uchar bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; fd_memset( bls, 0xBB, sizeof(bls) ); - fd_vote_stakes_snap_insert_t_1( vote_stakes, fork_id, &v, &v, stake, commission, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &v, &v, stake, commission, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_1( vote_stakes, fork_id, &v, &v, stake, commission, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &v, &v, stake, commission, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_update_state( vote_stakes, fork_id, &v, 0UL, voter_vote_ts( i ), 1 ); } add_delegated_stake_account ( env, &s, &v, stake ); @@ -715,6 +717,111 @@ test_enforce_registration_v5_invalid_location( fd_wksp_t * wksp ) { voter0_is_v5 = 0; } +static int +loc_eq( fd_validator_location_t a, + fd_validator_location_t b ) { + return a.x==b.x && a.y==b.y && a.z==b.z; +} + +static void +test_location_captured( fd_wksp_t * wksp ) { + voter0_is_v5 = 1; + test_env_t env[1]; + test_env_create( env, wksp ); + advance_into_epoch( env, TEST_VAT_EPOCH-1UL ); + + fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( env->bank ); + ulong fork_id = env->bank->vote_stakes_fork_id; + fd_pubkey_t v0 = vote_key( 0UL ); + fd_pubkey_t v1 = vote_key( 1UL ); + fd_vote_stakes_ele_t q[1]; + FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, fork_id, &v0, q ) && loc_eq( q->location, LOC_FRA ) ); + FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, fork_id, &v1, q ) && loc_eq( q->location, no_loc ) ); + FD_TEST( fd_vote_stakes_query_t_2( vote_stakes, fork_id, &v0, q ) && loc_eq( q->location, LOC_FRA ) ); + + fd_vote_stake_weight_t weights[ NUM_VOTERS ]; + ulong cnt = fd_stake_weights_by_node( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, weights ); + FD_TEST( cnt==NUM_VAT_ELIGIBLE ); + ulong seen = 0UL; + for( ulong i=0UL; ibank; + ulong const spe = TEST_SLOTS_PER_EPOCH; + + bank->f.features.geo_aware_leader_schedule = FD_FEATURE_DISABLED; + FD_TEST( !fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 0UL ) ); + FD_TEST( !fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 100UL ) ); + + bank->f.features.geo_aware_leader_schedule = 0UL; + FD_TEST( !fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 0UL ) ); + FD_TEST( fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 1UL ) ); + + bank->f.features.geo_aware_leader_schedule = 3UL*spe+5UL; + FD_TEST( !fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 3UL ) ); + FD_TEST( fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 4UL ) ); + FD_TEST( fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 9UL ) ); + + bank->f.features.geo_aware_leader_schedule = 4UL*spe-1UL; + FD_TEST( !fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 3UL ) ); + FD_TEST( fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 4UL ) ); + + bank->f.features.geo_aware_leader_schedule = 3UL*spe; + FD_TEST( !fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 3UL ) ); + FD_TEST( fd_epoch_leaders_use_geo_aware( &bank->f.features, &bank->f.epoch_schedule, 4UL ) ); + + test_env_destroy( env ); +} + +static void +test_geo_schedule_enforced_set( fd_wksp_t * wksp ) { + voter0_is_v5 = 1; + test_env_t env[1]; + test_env_create( env, wksp ); + enable_feature( env, offsetof( fd_features_t, enforce_validator_location_registration ), 0UL ); + enable_feature_pending( env, offsetof( fd_features_t, geo_aware_leader_schedule ) ); + advance_into_epoch( env, 2UL ); + + FD_TEST( env->bank->f.features.geo_aware_leader_schedule==TEST_SLOTS_PER_EPOCH ); + FD_TEST( fd_epoch_leaders_use_geo_aware( &env->bank->f.features, &env->bank->f.epoch_schedule, 2UL ) ); + fd_epoch_leaders_t const * leaders = fd_bank_epoch_leaders_query( env->bank, 2UL ); + FD_TEST( leaders && leaders->pub_cnt==1UL ); + fd_pubkey_t v0 = vote_key( 0UL ); + for( ulong i=0UL; isched_cnt; i++ ) FD_TEST( fd_pubkey_eq( leaders->vote_addr+i, &v0 ) ); + + test_env_destroy( env ); + voter0_is_v5 = 0; +} + +static void +test_geo_schedule_unlocated_leader_err( fd_wksp_t * wksp ) { + pid_t pid = fork(); + FD_TEST( pid>=0 ); + if( !pid ) { + fd_log_level_logfile_set( 6 ); + fd_log_level_stderr_set( 6 ); + test_env_t env[1]; + test_env_create( env, wksp ); + enable_feature_pending( env, offsetof( fd_features_t, geo_aware_leader_schedule ) ); + advance_into_epoch( env, 2UL ); + _exit( 0 ); + } + int status = 0; + FD_TEST( waitpid( pid, &status, 0 )==pid ); + FD_TEST( WIFEXITED( status ) && WEXITSTATUS( status )==1 ); +} + /* Whether the whale's last vote made it into the clock this epoch, inferred from the stake-weighted timestamp: 10s when the dominant future-voting whale is counted, 3s when it is not. */ @@ -828,7 +935,11 @@ main( int argc, char ** argv ) { test_enforce_registration_from_genesis( wksp ); test_enforce_registration_activates_at_boundary( wksp ); test_enforce_registration_v5_invalid_location( wksp ); + test_location_captured( wksp ); test_vat_invalidate_revalidate( wksp ); + test_geo_leader_schedule_gate( wksp ); + test_geo_schedule_enforced_set( wksp ); + test_geo_schedule_unlocated_leader_err( wksp ); fd_wksp_delete_anonymous( wksp ); FD_LOG_NOTICE(( "pass" )); diff --git a/src/flamenco/runtime/tests/fd_block_harness.c b/src/flamenco/runtime/tests/fd_block_harness.c index 65f6a98b844..e9d094d792a 100644 --- a/src/flamenco/runtime/tests/fd_block_harness.c +++ b/src/flamenco/runtime/tests/fd_block_harness.c @@ -82,10 +82,11 @@ fd_solfuzz_block_update_prev_epoch_stakes( fd_vote_stakes_t * vo } uchar const no_bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ] = {0}; + fd_validator_location_t const no_loc = {0}; if( use_t_1 ) fd_vote_stakes_snap_insert_t_1( vote_stakes, vote_stakes_fork_id, &vote_pubkey, &node_pubkey, stake, commission, no_bls, - block_revenue_commission_bps, pending_delegator_rewards, inflation_collector, block_collector ); + block_revenue_commission_bps, pending_delegator_rewards, inflation_collector, block_collector, &no_loc ); else fd_vote_stakes_snap_insert_t_2( vote_stakes, vote_stakes_fork_id, &vote_pubkey, &node_pubkey, stake, commission, no_bls, - block_revenue_commission_bps, pending_delegator_rewards, inflation_collector, block_collector ); + block_revenue_commission_bps, pending_delegator_rewards, inflation_collector, block_collector, &no_loc ); } } diff --git a/src/flamenco/runtime/tests/fd_svm_mini.c b/src/flamenco/runtime/tests/fd_svm_mini.c index f91452988f2..e0b7138fa06 100644 --- a/src/flamenco/runtime/tests/fd_svm_mini.c +++ b/src/flamenco/runtime/tests/fd_svm_mini.c @@ -419,11 +419,12 @@ fd_svm_mini_init_mock_validators( fd_svm_mini_t * mini, /* Populate bank structures */ uchar const no_bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ] = {0}; /* zeroed means no key is registered */ + fd_validator_location_t const no_loc = {0}; fd_pubkey_t const * block_collector = params->mock_block_collector_self ? &vote_key : params->mock_block_collector; fd_vote_stakes_snap_insert_t_1( vote_stakes, fork_id, &vote_key, &identity_key, uniform_stake, 1234U, no_bls, - params->mock_block_revenue_commission_bps, 0UL, NULL, block_collector ); + params->mock_block_revenue_commission_bps, 0UL, NULL, block_collector, &no_loc ); fd_vote_stakes_snap_insert_t_2( vote_stakes, fork_id, &vote_key, &identity_key, uniform_stake, 1234U, no_bls, - params->mock_block_revenue_commission_bps, 0UL, NULL, block_collector ); + params->mock_block_revenue_commission_bps, 0UL, NULL, block_collector, &no_loc ); fd_vote_stakes_update_state( vote_stakes, fork_id, &vote_key, 0UL, 0L, 1 ); fd_epoch_credits_t * epoch_credits = &epoch_credits_view->credits[ i ]; @@ -458,7 +459,7 @@ fd_svm_mini_init_mock_validators( fd_svm_mini_t * mini, void * leaders_mem = fd_bank_epoch_leaders_modify( bank, epoch ); FD_TEST( fd_epoch_leaders_join( fd_epoch_leaders_new( - leaders_mem, epoch, slot0, slot_cnt, N, stakes ) ) ); + leaders_mem, epoch, slot0, slot_cnt, N, stakes, 0 ) ) ); fd_rng_delete( fd_rng_leave( rng ) ); free( stakes ); diff --git a/src/flamenco/runtime/tests/test_fee_reward.c b/src/flamenco/runtime/tests/test_fee_reward.c index 1e180d04683..b8b8cece9a5 100644 --- a/src/flamenco/runtime/tests/test_fee_reward.c +++ b/src/flamenco/runtime/tests/test_fee_reward.c @@ -144,7 +144,7 @@ test_leader_does_not_exist( fd_svm_mini_t * mini ) { ulong slot_cnt = bank->f.epoch_schedule.slots_per_epoch; void * leaders_mem = fd_bank_epoch_leaders_modify( bank, epoch ); FD_TEST( fd_epoch_leaders_join( fd_epoch_leaders_new( - leaders_mem, epoch, slot0, slot_cnt, 1UL, &stake ) ) ); + leaders_mem, epoch, slot0, slot_cnt, 1UL, &stake, 0 ) ) ); /* Verify the leader account does NOT exist */ FD_TEST( read_lamports( mini, fork_id, &leader_key ) == 0UL ); @@ -198,7 +198,7 @@ test_payout_below_rent_exempt( fd_svm_mini_t * mini ) { ulong slot_cnt = bank->f.epoch_schedule.slots_per_epoch; void * leaders_mem = fd_bank_epoch_leaders_modify( bank, epoch ); FD_TEST( fd_epoch_leaders_join( fd_epoch_leaders_new( - leaders_mem, epoch, slot0, slot_cnt, 1UL, &stake ) ) ); + leaders_mem, epoch, slot0, slot_cnt, 1UL, &stake, 0 ) ) ); /* Leader does not exist => starts with 0 lamports. Set fees to 1 lamport, well below rent-exempt threshold. */ @@ -247,7 +247,7 @@ test_leader_not_system_owned( fd_svm_mini_t * mini ) { ulong slot_cnt = bank->f.epoch_schedule.slots_per_epoch; void * leaders_mem = fd_bank_epoch_leaders_modify( bank, epoch ); FD_TEST( fd_epoch_leaders_join( fd_epoch_leaders_new( - leaders_mem, epoch, slot0, slot_cnt, 1UL, &stake ) ) ); + leaders_mem, epoch, slot0, slot_cnt, 1UL, &stake, 0 ) ) ); /* Create the leader account owned by the vote program (not system) */ { @@ -718,7 +718,7 @@ make_leader_schedule( fd_bank_t * bank, fd_pubkey_t const * leader_key ) { ulong slot_cnt = bank->f.epoch_schedule.slots_per_epoch; void * leaders_mem = fd_bank_epoch_leaders_modify( bank, epoch ); FD_TEST( fd_epoch_leaders_join( fd_epoch_leaders_new( - leaders_mem, epoch, slot0, slot_cnt, 1UL, &stake ) ) ); + leaders_mem, epoch, slot0, slot_cnt, 1UL, &stake, 0 ) ) ); } /* relax_post_exec_min_balance_check matrix on the pre-feature fee diff --git a/src/flamenco/runtime/tests/test_inflation_rewards.c b/src/flamenco/runtime/tests/test_inflation_rewards.c index 84b2292c201..b1c89b68242 100644 --- a/src/flamenco/runtime/tests/test_inflation_rewards.c +++ b/src/flamenco/runtime/tests/test_inflation_rewards.c @@ -35,6 +35,8 @@ static fd_sol_sysvar_clock_t const test_ag_clock = { .leader_schedule_epoch = 2UL, }; +static fd_validator_location_t const no_loc = {0}; + static ulong read_lamports( fd_svm_mini_t * mini, fd_accdb_fork_id_t fork_id, @@ -470,16 +472,16 @@ test_footer_uses_vote_stakes_rank( fd_svm_mini_t * mini, bank->vote_stakes_fork_id = fd_vote_stakes_init( vote_stakes, bank->f.epoch ); if( use_t_3 ) { - fd_vote_stakes_snap_insert_t_2( vote_stakes, bank->vote_stakes_fork_id, &vote_b, &identity_b, 200UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, bank->vote_stakes_fork_id, &vote_a, &identity_a, 100UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, bank->vote_stakes_fork_id, &vote_b, &identity_b, 200UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, bank->vote_stakes_fork_id, &vote_a, &identity_a, 100UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_finalize( vote_stakes, bank->vote_stakes_fork_id, FD_VOTE_STAKES_ITER_T_2 ); - fd_vote_stakes_snap_insert_t_n( vote_stakes, bank->vote_stakes_fork_id, 3UL, &vote_a, &identity_a, 200UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_n( vote_stakes, bank->vote_stakes_fork_id, 3UL, &vote_b, &identity_b, 100UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, bank->vote_stakes_fork_id, 3UL, &vote_a, &identity_a, 200UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, bank->vote_stakes_fork_id, 3UL, &vote_b, &identity_b, 100UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_finalize( vote_stakes, bank->vote_stakes_fork_id, FD_VOTE_STAKES_ITER_T_3 ); } else { - fd_vote_stakes_snap_insert_t_2( vote_stakes, bank->vote_stakes_fork_id, &vote_a, &identity_a, 200UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, bank->vote_stakes_fork_id, &vote_b, &identity_b, 100UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, bank->vote_stakes_fork_id, &vote_a, &identity_a, 200UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, bank->vote_stakes_fork_id, &vote_b, &identity_b, 100UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_finalize( vote_stakes, bank->vote_stakes_fork_id, FD_VOTE_STAKES_ITER_T_2 ); } @@ -645,10 +647,10 @@ test_footer_fused_matches_sequential( fd_svm_mini_t * mini ) { ulong vs_fork = fd_vote_stakes_init( vote_stakes, 1UL ); for( ulong i=0UL; if.features, alpenglow, 0UL ); bank->vote_stakes_fork_id = vs_fork; @@ -921,7 +923,7 @@ test_finalize_verifies_footer( fd_svm_mini_t * mini ) { fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank[ 0 ] ); fd_vote_stakes_reset( vote_stakes ); ulong vs_fork = fd_vote_stakes_init( vote_stakes, 1UL ); - for( ulong i=0UL; i<2UL; i++ ) fd_vote_stakes_snap_insert_t_2( vote_stakes, vs_fork, vote+i, identity+i, 1000UL*(i+1UL), 0U, bls[i], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + for( ulong i=0UL; i<2UL; i++ ) fd_vote_stakes_snap_insert_t_2( vote_stakes, vs_fork, vote+i, identity+i, 1000UL*(i+1UL), 0U, bls[i], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_finalize( vote_stakes, vs_fork, FD_VOTE_STAKES_ITER_T_2 ); fd_sol_sysvar_clock_t clock_[1]; @@ -1070,9 +1072,9 @@ test_alpenglow_reward_uses_vote_credits( fd_svm_mini_t * mini ) { fd_vote_stakes_reset( vote_stakes ); epoch_bank->vote_stakes_fork_id = fd_vote_stakes_init( vote_stakes, epoch_bank->f.epoch ); uchar no_bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ] = {0}; - fd_vote_stakes_snap_insert_t_1( vote_stakes, epoch_bank->vote_stakes_fork_id, &vote_key, &identity_key, 1000000000UL, 0U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, epoch_bank->vote_stakes_fork_id, &vote_key, &identity_key, 1000000000UL, 0U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_n( vote_stakes, epoch_bank->vote_stakes_fork_id, 3UL, &vote_key, &identity_key, 1000000000UL, 1234U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_1( vote_stakes, epoch_bank->vote_stakes_fork_id, &vote_key, &identity_key, 1000000000UL, 0U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, epoch_bank->vote_stakes_fork_id, &vote_key, &identity_key, 1000000000UL, 0U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, epoch_bank->vote_stakes_fork_id, 3UL, &vote_key, &identity_key, 1000000000UL, 1234U, no_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); FD_FEATURE_SET_ACTIVE( &epoch_bank->f.features, delay_commission_updates, 0UL ); fd_stake_rewards_clear( fd_bank_stake_rewards_modify( epoch_bank ) ); diff --git a/src/flamenco/stakes/fd_stake_weight.h b/src/flamenco/stakes/fd_stake_weight.h index 7bc903fe423..33051310af2 100644 --- a/src/flamenco/stakes/fd_stake_weight.h +++ b/src/flamenco/stakes/fd_stake_weight.h @@ -4,6 +4,7 @@ /* fd_stake_weight.h provides utils for dealing with stake weight sets. */ #include "../fd_flamenco_base.h" +#include "../runtime/program/vote/fd_validator_location.h" struct fd_stake_weight { fd_pubkey_t key; /* validator identity pubkey */ @@ -12,10 +13,11 @@ struct fd_stake_weight { typedef struct fd_stake_weight fd_stake_weight_t; struct fd_vote_stake_weight { - fd_pubkey_t vote_key; /* vote account pubkey */ - fd_pubkey_t id_key; /* validator identity pubkey */ - ulong stake; /* total stake by vote account */ - uchar bls_key[ 48 ]; /* compressed BLS voting pubkey, zero if unregistered */ + fd_pubkey_t vote_key; /* vote account pubkey */ + fd_pubkey_t id_key; /* validator identity pubkey */ + ulong stake; /* total stake by vote account */ + uchar bls_key[ 48 ]; /* compressed BLS voting pubkey, zero if unregistered */ + fd_validator_location_t location; /* SIMD-0674: Validator Location Registration */ }; typedef struct fd_vote_stake_weight fd_vote_stake_weight_t; diff --git a/src/flamenco/stakes/fd_stakes.c b/src/flamenco/stakes/fd_stakes.c index b3859baed87..f022d751fc5 100644 --- a/src/flamenco/stakes/fd_stakes.c +++ b/src/flamenco/stakes/fd_stakes.c @@ -476,6 +476,7 @@ fd_stake_weights_by_node( fd_vote_stakes_t * vote_stakes, fd_memcpy( weights[ weights_cnt ].vote_key.uc, &ele->pubkey, sizeof(fd_pubkey_t) ); fd_memcpy( weights[ weights_cnt ].id_key.uc, &ele->node_account, sizeof(fd_pubkey_t) ); fd_memcpy( weights[ weights_cnt ].bls_key, ele->bls_key, sizeof(weights[ weights_cnt ].bls_key) ); + weights[ weights_cnt ].location = ele->location; weights[ weights_cnt ].stake = ele->stake; weights_cnt++; } @@ -682,8 +683,9 @@ fd_refresh_vote_accounts( fd_bank_t * bank, FD_TEST( !fd_vote_account_pending_delegator_rewards( acc.data, acc.data_len, &pending_delegator_rewards ) ); FD_TEST( !fd_vote_account_collectors( acc.data, acc.data_len, &stake_accum->pubkey, &node_account_t_1, &inflation_collector, &block_collector ) ); + fd_validator_location_t location = fd_vote_account_location( acc.data, acc.data_len ); fd_vote_stakes_insert( vote_stakes, fork_id, &stake_accum->pubkey, &node_account_t_1, stake_t_1, commission_t_1, bls_key_t_1, - block_revenue_commission_bps, pending_delegator_rewards, &inflation_collector, &block_collector ); + block_revenue_commission_bps, pending_delegator_rewards, &inflation_collector, &block_collector, &location ); top_votes_eligible++; fd_accdb_unread_one( accdb, &acc ); } diff --git a/src/flamenco/stakes/fd_vote_stakes.c b/src/flamenco/stakes/fd_vote_stakes.c index 895a273c0e0..7e06c99f96f 100644 --- a/src/flamenco/stakes/fd_vote_stakes.c +++ b/src/flamenco/stakes/fd_vote_stakes.c @@ -15,20 +15,21 @@ #define FD_VOTE_STAKES_EPOCH_CACHE_CNT (5UL) /* t-2..t-5 sets plus one so crossing into t+1 evicts t-6 */ struct vacc { - fd_pubkey_t pubkey; - fd_pubkey_t node_account; - ulong stake; - ushort commission; /* inflation rewards, basis points */ - ushort block_revenue_commission_bps; /* SIMD-0123 */ - ulong pending_delegator_rewards; /* SIMD-0123, as of the set's epoch boundary */ - fd_pubkey_t inflation_collector; /* SIMD-0232, defaults to pubkey */ - fd_pubkey_t block_collector; /* SIMD-0232, defaults to node_account */ - ushort rank; - uchar bls_key[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; /* zero if unregistered */ - uchar bls_key_uncompressed[ FD_BLS_PUBKEY_UNCOMPRESSED_SZ ]; /* decompressed by finalize, valid iff rank!=NULL */ - uint left; - uint right; - uint next; + fd_pubkey_t pubkey; + fd_pubkey_t node_account; + ulong stake; + ushort commission; /* inflation rewards, basis points */ + ushort block_revenue_commission_bps; /* SIMD-0123 */ + ulong pending_delegator_rewards; /* SIMD-0123, as of the set's epoch boundary */ + fd_pubkey_t inflation_collector; /* SIMD-0232, defaults to pubkey */ + fd_pubkey_t block_collector; /* SIMD-0232, defaults to node_account */ + ushort rank; + uchar bls_key[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; /* zero if unregistered */ + uchar bls_key_uncompressed[ FD_BLS_PUBKEY_UNCOMPRESSED_SZ ]; /* decompressed by finalize, valid iff rank!=NULL */ + fd_validator_location_t location; + uint left; + uint right; + uint next; }; typedef struct vacc vacc_t; @@ -571,16 +572,17 @@ fd_vote_stakes_reset( fd_vote_stakes_t * vote_stakes ) { } static void -vacc_init( vacc_t * vacc, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ) { +vacc_init( vacc_t * vacc, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ) { vacc->pubkey = *pubkey; vacc->node_account = *node_account; vacc->stake = stake; @@ -590,21 +592,23 @@ vacc_init( vacc_t * vacc, vacc->inflation_collector = inflation_collector_opt ? *inflation_collector_opt : *pubkey; vacc->block_collector = block_collector_opt ? *block_collector_opt : *node_account; vacc->rank = FD_VOTE_STAKES_ALPENGLOW_RANK_NULL; + vacc->location = *location; memcpy( vacc->bls_key, bls_key, FD_BLS_PUBKEY_COMPRESSED_SZ ); } void -fd_vote_stakes_snap_insert_t_1( fd_vote_stakes_t * vote_stakes, - ulong fork_id, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ) { +fd_vote_stakes_snap_insert_t_1( fd_vote_stakes_t * vote_stakes, + ulong fork_id, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ) { if( FD_UNLIKELY( !stake ) ) return; ulong cache_idx = t_1_lock( vote_stakes, fork_id_t_1_idx( fork_id ), 1 ); @@ -613,23 +617,24 @@ fd_vote_stakes_snap_insert_t_1( fd_vote_stakes_t * vote_stakes, vacc_t * vacc = vacc_pool_ele_acquire( pool ); vacc_init( vacc, pubkey, node_account, stake, commission, bls_key, - block_revenue_commission_bps, pending_delegator_rewards, inflation_collector_opt, block_collector_opt ); + block_revenue_commission_bps, pending_delegator_rewards, inflation_collector_opt, block_collector_opt, location ); FD_TEST( vacc_map_ele_insert( map, vacc, pool ) ); t_1_unlock( vote_stakes, 1 ); } void -fd_vote_stakes_snap_insert_t_2( fd_vote_stakes_t * vote_stakes, - ulong fork_id, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ) { +fd_vote_stakes_snap_insert_t_2( fd_vote_stakes_t * vote_stakes, + ulong fork_id, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ) { if( FD_UNLIKELY( !stake ) ) return; ulong epoch_idx = (ulong)fork_id_epoch( fork_id ) % FD_VOTE_STAKES_EPOCH_CACHE_CNT; @@ -638,23 +643,24 @@ fd_vote_stakes_snap_insert_t_2( fd_vote_stakes_t * vote_stakes, vacc_t * vacc = vacc_pool_ele_acquire( pool ); vacc_init( vacc, pubkey, node_account, stake, commission, bls_key, - block_revenue_commission_bps, pending_delegator_rewards, inflation_collector_opt, block_collector_opt ); + block_revenue_commission_bps, pending_delegator_rewards, inflation_collector_opt, block_collector_opt, location ); FD_TEST( vacc_map_ele_insert( map, vacc, pool ) ); } void -fd_vote_stakes_snap_insert_t_n( fd_vote_stakes_t * vote_stakes, - ulong fork_id, - ulong n, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ) { +fd_vote_stakes_snap_insert_t_n( fd_vote_stakes_t * vote_stakes, + ulong fork_id, + ulong n, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ) { ulong epoch = (ulong)fork_id_epoch( fork_id ); ulong back = n-2UL; if( FD_UNLIKELY( epocht_2_epoch[ t_n_idx ] = t_n_epoch; } static void -t_1_insert( fd_vote_stakes_t * vote_stakes, - vacc_t * pool, - vacc_map_t * map, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ) { +t_1_insert( fd_vote_stakes_t * vote_stakes, + vacc_t * pool, + vacc_map_t * map, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ) { vacc_heap_t * heap = vacc_heap( vote_stakes ); if( FD_UNLIKELY( stake==0UL || stake<=vote_stakes->min_stake_wmark ) ) return; @@ -704,27 +711,28 @@ t_1_insert( fd_vote_stakes_t * vote_stakes, vacc_t * vacc = vacc_pool_ele_acquire( pool ); vacc_init( vacc, pubkey, node_account, stake, commission, bls_key, - block_revenue_commission_bps, pending_delegator_rewards, inflation_collector_opt, block_collector_opt ); + block_revenue_commission_bps, pending_delegator_rewards, inflation_collector_opt, block_collector_opt, location ); vacc_heap_ele_insert( heap, vacc, pool ); FD_TEST( vacc_map_ele_insert( map, vacc, pool ) ); } void -fd_vote_stakes_insert( fd_vote_stakes_t * vote_stakes, - ulong fork_id, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ) { +fd_vote_stakes_insert( fd_vote_stakes_t * vote_stakes, + ulong fork_id, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ) { ulong cache_idx = t_1_lock( vote_stakes, fork_id_t_1_idx( fork_id ), 1 ); t_1_insert( vote_stakes, t_1_cache_pool( vote_stakes, cache_idx ), t_1_cache_map( vote_stakes, cache_idx ), pubkey, node_account, stake, commission, bls_key, - block_revenue_commission_bps, pending_delegator_rewards, inflation_collector_opt, block_collector_opt ); + block_revenue_commission_bps, pending_delegator_rewards, inflation_collector_opt, block_collector_opt, location ); t_1_unlock( vote_stakes, 1 ); } @@ -854,6 +862,7 @@ fd_vote_stakes_new_fork( fd_vote_stakes_t * vote_stakes, dst->pending_delegator_rewards = src->pending_delegator_rewards; dst->inflation_collector = src->inflation_collector; dst->block_collector = src->block_collector; + dst->location = src->location; dst->rank = src->rank; memcpy( dst->bls_key, src->bls_key, FD_BLS_PUBKEY_COMPRESSED_SZ ); memcpy( dst->bls_key_uncompressed, src->bls_key_uncompressed, FD_BLS_PUBKEY_UNCOMPRESSED_SZ ); @@ -977,6 +986,7 @@ vacc_ele( vacc_t const * vacc, .inflation_collector = vacc->inflation_collector, .block_collector = vacc->block_collector, .rank = vacc->rank, + .location = vacc->location, }; memcpy( ele_out->bls_key, vacc->bls_key, FD_BLS_PUBKEY_COMPRESSED_SZ ); memcpy( ele_out->bls_key_uncompressed, vacc->bls_key_uncompressed, FD_BLS_PUBKEY_UNCOMPRESSED_SZ ); diff --git a/src/flamenco/stakes/fd_vote_stakes.h b/src/flamenco/stakes/fd_vote_stakes.h index 95cb18ea336..ff9374df04c 100644 --- a/src/flamenco/stakes/fd_vote_stakes.h +++ b/src/flamenco/stakes/fd_vote_stakes.h @@ -50,22 +50,23 @@ typedef struct fd_vote_stakes fd_vote_stakes_t; set. */ struct fd_vote_stakes_ele { - fd_pubkey_t pubkey; - fd_pubkey_t node_account; - ulong stake; - ushort commission; /* inflation rewards, basis points */ - ushort block_revenue_commission_bps; /* SIMD-0123 */ - ulong pending_delegator_rewards; /* SIMD-0123 */ - fd_pubkey_t inflation_collector; /* SIMD-0232 */ - fd_pubkey_t block_collector; /* SIMD-0232 */ - ushort rank; - uchar bls_key[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; - uchar bls_key_uncompressed[ FD_BLS_PUBKEY_UNCOMPRESSED_SZ ]; + fd_pubkey_t pubkey; + fd_pubkey_t node_account; + ulong stake; + ushort commission; /* inflation rewards, basis points */ + ushort block_revenue_commission_bps; /* SIMD-0123 */ + ulong pending_delegator_rewards; /* SIMD-0123 */ + fd_pubkey_t inflation_collector; /* SIMD-0232 */ + fd_pubkey_t block_collector; /* SIMD-0232 */ + ushort rank; + uchar bls_key[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; + uchar bls_key_uncompressed[ FD_BLS_PUBKEY_UNCOMPRESSED_SZ ]; + fd_validator_location_t location; /* The fork's vote state, only set for t-2 (zero otherwise). */ - ulong last_vote_slot; - long last_vote_ts; - uchar is_valid; + ulong last_vote_slot; + long last_vote_ts; + uchar is_valid; }; typedef struct fd_vote_stakes_ele fd_vote_stakes_ele_t; @@ -111,34 +112,36 @@ fd_vote_stakes_reset( fd_vote_stakes_t * vote_stakes ); only be used when loading in vote accounts from a snapshot. */ void -fd_vote_stakes_snap_insert_t_1( fd_vote_stakes_t * vote_stakes, - ulong fork_id, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ); +fd_vote_stakes_snap_insert_t_1( fd_vote_stakes_t * vote_stakes, + ulong fork_id, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ); /* fd_vote_stakes_snap_insert_t_2 inserts a new vote account into the t-2 set. This skips over any vote account validation and should only be used when loading in vote accounts from a snapshot. */ void -fd_vote_stakes_snap_insert_t_2( fd_vote_stakes_t * vote_stakes, - ulong fork_id, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ); +fd_vote_stakes_snap_insert_t_2( fd_vote_stakes_t * vote_stakes, + ulong fork_id, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ); /* fd_vote_stakes_snap_insert_t_n inserts a new vote account into the t-n set, n in 3..5, and marks that set resident. This skips over @@ -146,18 +149,19 @@ fd_vote_stakes_snap_insert_t_2( fd_vote_stakes_t * vote_stakes, vote accounts from a snapshot. */ void -fd_vote_stakes_snap_insert_t_n( fd_vote_stakes_t * vote_stakes, - ulong fork_id, - ulong n, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ); +fd_vote_stakes_snap_insert_t_n( fd_vote_stakes_t * vote_stakes, + ulong fork_id, + ulong n, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ); /* fd_vote_stakes_insert inserts a new vote account into the t-1 set. This can be called repeatedly after a boundary-crossing @@ -166,17 +170,18 @@ fd_vote_stakes_snap_insert_t_n( fd_vote_stakes_t * vote_stakes, The top 2000 vote accounts by stake are inserted into the t-1 set. */ void -fd_vote_stakes_insert( fd_vote_stakes_t * vote_stakes, - ulong fork_id, - fd_pubkey_t const * pubkey, - fd_pubkey_t const * node_account, - ulong stake, - ushort commission, - uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], - ushort block_revenue_commission_bps, - ulong pending_delegator_rewards, - fd_pubkey_t const * inflation_collector_opt, - fd_pubkey_t const * block_collector_opt ); +fd_vote_stakes_insert( fd_vote_stakes_t * vote_stakes, + ulong fork_id, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * node_account, + ulong stake, + ushort commission, + uchar const bls_key[ static FD_BLS_PUBKEY_COMPRESSED_SZ ], + ushort block_revenue_commission_bps, + ulong pending_delegator_rewards, + fd_pubkey_t const * inflation_collector_opt, + fd_pubkey_t const * block_collector_opt, + fd_validator_location_t const * location ); /* fd_vote_stakes_finalize assigns Alpenglow ranks to the set that iter_kind addresses for fork_id. Accounts with invalid or infinity diff --git a/src/flamenco/stakes/test_vote_stakes.c b/src/flamenco/stakes/test_vote_stakes.c index ec34779fd06..2c5d8e41f29 100644 --- a/src/flamenco/stakes/test_vote_stakes.c +++ b/src/flamenco/stakes/test_vote_stakes.c @@ -8,6 +8,8 @@ #include #include +static fd_validator_location_t const no_loc = {0}; + static fd_pubkey_t key( ulong x ) { return (fd_pubkey_t){ .ul = { x } }; @@ -46,14 +48,14 @@ test_t_1_spill( int disk_fd ) { ulong root = fd_vote_stakes_init( vote_stakes, 0UL ); fd_pubkey_t root_vote = key( 200UL ); - fd_vote_stakes_snap_insert_t_1( vote_stakes, root, &root_vote, &node, 1000UL, 1U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_1( vote_stakes, root, &root_vote, &node, 1000UL, 1U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); /* Three forks cross the same boundary, each with its own t-1 set. */ ulong forks[ 3 ]; for( ulong i=0UL; i<3UL; i++ ) { forks[ i ] = fd_vote_stakes_new_fork( vote_stakes, root, 1UL ); fd_pubkey_t vote = key( 300UL+i ); - fd_vote_stakes_insert( vote_stakes, forks[ i ], &vote, &node, 2000UL+i, 2U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_insert( vote_stakes, forks[ i ], &vote, &node, 2000UL+i, 2U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); } struct stat st; @@ -133,11 +135,11 @@ test_t_1_concurrent( int disk_fd ) { fd_pubkey_t node = key( 100UL ); fd_pubkey_t root_vote = key( 200UL ); ulong root = fd_vote_stakes_init( conc_vs, 0UL ); - fd_vote_stakes_snap_insert_t_1( conc_vs, root, &root_vote, &node, 1000UL, 1U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_1( conc_vs, root, &root_vote, &node, 1000UL, 1U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); for( ulong i=0UL; i<3UL; i++ ) { conc_forks[ i ] = fd_vote_stakes_new_fork( conc_vs, root, 1UL ); fd_pubkey_t vote = key( 300UL+i ); - fd_vote_stakes_insert( conc_vs, conc_forks[ i ], &vote, &node, 2000UL+i, 2U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_insert( conc_vs, conc_forks[ i ], &vote, &node, 2000UL+i, 2U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); } FD_VOLATILE( conc_stop ) = 0; @@ -156,6 +158,82 @@ test_t_1_concurrent( int disk_fd ) { free( mem ); } +static int +loc_eq( fd_validator_location_t a, + fd_validator_location_t b ) { + return a.x==b.x && a.y==b.y && a.z==b.z; +} + +static void +test_location( int disk_fd ) { + FD_TEST( !ftruncate( disk_fd, 0L ) ); + ulong footprint = fd_vote_stakes_footprint( 16UL, 2UL ); + void * mem = aligned_alloc( fd_vote_stakes_align(), footprint ); + FD_TEST( mem ); + fd_vote_stakes_t * vote_stakes = fd_vote_stakes_join( fd_vote_stakes_new( mem, disk_fd, 16UL, 2UL, 1234UL ), disk_fd ); + FD_TEST( vote_stakes ); + + uchar bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ] = { 1 }; + fd_pubkey_t node = key( 100UL ); + fd_validator_location_t const fra = { 4051542, 618526, 4870645 }; + fd_validator_location_t const tyo = { -3955214, 3355452, 3699409 }; + fd_validator_location_t const syd = { -4646180, 2553206, -3534375 }; + + ulong root = fd_vote_stakes_init( vote_stakes, 5UL ); + fd_pubkey_t t1_vote = key( 200UL ); + fd_pubkey_t zero_vote = key( 201UL ); + fd_pubkey_t t2_vote = key( 202UL ); + fd_pubkey_t t3_vote = key( 203UL ); + fd_vote_stakes_snap_insert_t_1( vote_stakes, root, &t1_vote, &node, 1000UL, 1U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &fra ); + fd_vote_stakes_snap_insert_t_1( vote_stakes, root, &zero_vote, &node, 900UL, 1U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &t2_vote, &node, 1000UL, 1U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &tyo ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, root, 3UL, &t3_vote, &node, 1000UL, 1U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &syd ); + + fd_vote_stakes_ele_t q[1]; + FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, root, &t1_vote, q ) && loc_eq( q->location, fra ) ); + FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, root, &zero_vote, q ) && loc_eq( q->location, no_loc ) ); + FD_TEST( fd_vote_stakes_query_t_2( vote_stakes, root, &t2_vote, q ) && loc_eq( q->location, tyo ) ); + FD_TEST( fd_vote_stakes_query_t_3( vote_stakes, root, &t3_vote, q ) && loc_eq( q->location, syd ) ); + + ulong forks[ 3 ]; + for( ulong i=0UL; i<3UL; i++ ) { + forks[ i ] = fd_vote_stakes_new_fork( vote_stakes, root, 6UL ); + fd_pubkey_t vote = key( 300UL+i ); + fd_validator_location_t loc = fra; + loc.x += (int)i; + fd_vote_stakes_insert( vote_stakes, forks[ i ], &vote, &node, 2000UL+i, 2U, bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &loc ); + } + + struct stat st; + FD_TEST( !fstat( disk_fd, &st ) && st.st_size>0L ); + + for( ulong i=0UL; i<3UL; i++ ) { + fd_pubkey_t vote = key( 300UL+i ); + fd_validator_location_t loc = fra; + loc.x += (int)i; + FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, forks[ i ], &vote, q ) && loc_eq( q->location, loc ) ); + FD_TEST( fd_vote_stakes_query_t_2( vote_stakes, forks[ i ], &t1_vote, q ) && loc_eq( q->location, fra ) ); + } + + uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ]; + ulong seen = 0UL; + for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, forks[ 1 ], FD_VOTE_STAKES_ITER_T_1, iter_mem ); + !fd_vote_stakes_iter_done( vote_stakes, forks[ 1 ], FD_VOTE_STAKES_ITER_T_1, iter ); + fd_vote_stakes_iter_next( vote_stakes, forks[ 1 ], FD_VOTE_STAKES_ITER_T_1, iter ) ) { + fd_vote_stakes_ele_t ele[1]; + fd_vote_stakes_iter_ele( vote_stakes, forks[ 1 ], FD_VOTE_STAKES_ITER_T_1, iter, ele ); + fd_validator_location_t loc = fra; + loc.x += 1; + FD_TEST( loc_eq( ele->location, loc ) ); + seen++; + } + FD_TEST( seen==1UL ); + + for( ulong i=0UL; i<3UL; i++ ) fd_vote_stakes_purge_fork( vote_stakes, forks[ i ] ); + fd_vote_stakes_purge_fork( vote_stakes, root ); + free( mem ); +} + int main( int argc, char ** argv ) { fd_boot( &argc, &argv ); @@ -165,6 +243,7 @@ main( int argc, char ** argv ) { test_t_1_spill( disk_fd ); test_t_1_concurrent( disk_fd ); + test_location( disk_fd ); ulong footprint = fd_vote_stakes_footprint( 16UL, 5UL ); void * mem = aligned_alloc( fd_vote_stakes_align(), footprint ); @@ -186,8 +265,8 @@ main( int argc, char ** argv ) { uchar bls_c[ FD_BLS_PUBKEY_COMPRESSED_SZ ] = { 9UL }; ulong root = fd_vote_stakes_init( vote_stakes, 0UL ); - fd_vote_stakes_snap_insert_t_1( vote_stakes, root, &vote_a, &node_a, 100UL, 10U, bls_a, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &vote_b, &node_b, 200UL, 20U, bls_b, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_1( vote_stakes, root, &vote_a, &node_a, 100UL, 10U, bls_a, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &vote_b, &node_b, 200UL, 20U, bls_b, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_update_state( vote_stakes, root, &vote_b, 7UL, 8L, 1 ); fd_vote_stakes_ele_t q[1]; @@ -231,7 +310,7 @@ main( int argc, char ** argv ) { fd_pubkey_t infl_c = key( 50UL ); fd_pubkey_t blk_c = key( 51UL ); - fd_vote_stakes_insert( vote_stakes, child, &vote_c, &node_c, 300UL, 30U, bls_c, 2500U, 777UL, &infl_c, &blk_c ); + fd_vote_stakes_insert( vote_stakes, child, &vote_c, &node_c, 300UL, 30U, bls_c, 2500U, 777UL, &infl_c, &blk_c, &no_loc ); FD_TEST( fd_vote_stakes_query_t_1( vote_stakes, child, &vote_c, q ) ); FD_TEST( q->stake==300UL && q->commission==30U ); @@ -325,7 +404,7 @@ main( int argc, char ** argv ) { fd_vote_stakes_reset( vote_stakes ); ulong snapshot_root = fd_vote_stakes_init( vote_stakes, 2UL ); - fd_vote_stakes_snap_insert_t_n( vote_stakes, snapshot_root, 3UL, &vote_b, &node_b, 200UL, 20U, bls_b, 4321U, 99UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, snapshot_root, 3UL, &vote_b, &node_b, 200UL, 20U, bls_b, 4321U, 99UL, NULL, NULL, &no_loc ); FD_TEST( fd_vote_stakes_query_t_3( vote_stakes, snapshot_root, &vote_b, q ) ); FD_TEST( fd_pubkey_eq( &q->node_account, &node_b ) && q->stake==200UL && q->commission==20U ); FD_TEST( fd_vote_stakes_total_stake( vote_stakes, 1UL )==200UL ); @@ -351,7 +430,7 @@ main( int argc, char ** argv ) { FD_TEST( fd_vote_stakes_iter_done( vote_stakes, forks[0], FD_VOTE_STAKES_ITER_T_5, fd_vote_stakes_iter_init( vote_stakes, forks[0], FD_VOTE_STAKES_ITER_T_5, epoch_iter_mem ) ) ); for( ulong e=0UL; e<5UL; e++ ) { fd_pubkey_t vote = key( 100UL+e ); - fd_vote_stakes_insert( vote_stakes, forks[e], &vote, &node_a, 100UL*(e+1UL), 0U, bls_a, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_insert( vote_stakes, forks[e], &vote, &node_a, 100UL*(e+1UL), 0U, bls_a, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); forks[e+1UL] = fd_vote_stakes_new_fork( vote_stakes, forks[e], e+1UL ); if( e<4UL ) fd_vote_stakes_purge_fork( vote_stakes, forks[e] ); } @@ -390,8 +469,8 @@ main( int argc, char ** argv ) { root = fd_vote_stakes_init( vote_stakes, 1UL ); fd_pubkey_t rotated_vote_a = key( 10UL ); fd_pubkey_t rotated_vote_b = key( 11UL ); - fd_vote_stakes_insert( vote_stakes, root, &rotated_vote_a, &node_a, 100UL, 0U, valid_bls[2], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_insert( vote_stakes, root, &rotated_vote_b, &node_b, 200UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_insert( vote_stakes, root, &rotated_vote_a, &node_a, 100UL, 0U, valid_bls[2], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_insert( vote_stakes, root, &rotated_vote_b, &node_b, 200UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_1, &rotated_vote_b )==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ); fd_vote_stakes_finalize( vote_stakes, root, FD_VOTE_STAKES_ITER_T_1 ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_1, &rotated_vote_b )==0U ); @@ -409,9 +488,9 @@ main( int argc, char ** argv ) { fd_pubkey_t rank_vote_a = key( 20UL ); fd_pubkey_t rank_vote_b = key( 21UL ); fd_pubkey_t rank_vote_c = key( 22UL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &rank_vote_a, &node_a, 100UL, 0U, valid_bls[2], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &rank_vote_b, &node_b, 200UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &rank_vote_c, &node_c, 200UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &rank_vote_a, &node_a, 100UL, 0U, valid_bls[2], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &rank_vote_b, &node_b, 200UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &rank_vote_c, &node_c, 200UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_finalize( vote_stakes, root, FD_VOTE_STAKES_ITER_T_2 ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_2, &rank_vote_c )==0U ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_2, &rank_vote_b )==1U ); @@ -419,7 +498,7 @@ main( int argc, char ** argv ) { fd_pubkey_t rank_vote_dup = key( 23UL ); fd_pubkey_t node_dup = key( 24UL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &rank_vote_dup, &node_dup, 300UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &rank_vote_dup, &node_dup, 300UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_finalize( vote_stakes, root, FD_VOTE_STAKES_ITER_T_2 ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_2, &rank_vote_c )==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_2, &rank_vote_dup )==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ); @@ -434,8 +513,8 @@ main( int argc, char ** argv ) { fd_pubkey_t duplicate_identity = key( 30UL ); fd_pubkey_t identity_vote_a = key( 31UL ); fd_pubkey_t identity_vote_b = key( 32UL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &identity_vote_a, &duplicate_identity, 100UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &identity_vote_b, &duplicate_identity, 200UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &identity_vote_a, &duplicate_identity, 100UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_2( vote_stakes, root, &identity_vote_b, &duplicate_identity, 200UL, 0U, valid_bls[1], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_finalize( vote_stakes, root, FD_VOTE_STAKES_ITER_T_2 ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_2, &identity_vote_a )==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_2, &identity_vote_b )==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ); @@ -448,9 +527,9 @@ main( int argc, char ** argv ) { fd_pubkey_t infinity_vote = key( 42UL ); uchar invalid_bls [ FD_BLS_PUBKEY_COMPRESSED_SZ ] = {0}; uchar infinity_bls[ FD_BLS_PUBKEY_COMPRESSED_SZ ] = {0xc0}; - fd_vote_stakes_snap_insert_t_n( vote_stakes, root, 3UL, &invalid_vote, &node_a, 300UL, 0U, invalid_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_n( vote_stakes, root, 3UL, &valid_vote, &node_b, 100UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_snap_insert_t_n( vote_stakes, root, 3UL, &infinity_vote, &node_c, 200UL, 0U, infinity_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, root, 3UL, &invalid_vote, &node_a, 300UL, 0U, invalid_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, root, 3UL, &valid_vote, &node_b, 100UL, 0U, valid_bls[0], FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_snap_insert_t_n( vote_stakes, root, 3UL, &infinity_vote, &node_c, 200UL, 0U, infinity_bls, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); fd_vote_stakes_finalize( vote_stakes, root, FD_VOTE_STAKES_ITER_T_3 ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_3, &invalid_vote )==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ); FD_TEST( epoch_rank( vote_stakes, root, FD_VOTE_STAKES_ITER_T_3, &infinity_vote )==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ); @@ -462,21 +541,21 @@ main( int argc, char ** argv ) { child = fd_vote_stakes_new_fork( vote_stakes, root, 1UL ); fd_pubkey_t tie_a = key( 10000UL ); fd_pubkey_t tie_b = key( 10001UL ); - fd_vote_stakes_insert( vote_stakes, child, &tie_a, &node_a, 10UL, 1U, bls_a, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); - fd_vote_stakes_insert( vote_stakes, child, &tie_b, &node_b, 10UL, 1U, bls_b, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL ); + fd_vote_stakes_insert( vote_stakes, child, &tie_a, &node_a, 10UL, 1U, bls_a, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); + fd_vote_stakes_insert( vote_stakes, child, &tie_b, &node_b, 10UL, 1U, bls_b, FD_VOTE_DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, 0UL, NULL, NULL, &no_loc ); for( ulong i=2UL; i