From 288b6b8d72ce9862b52693dbec7cd66b55988d49 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/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 | 109 +++-- src/discof/backup/test_snap_roundtrip.c | 33 +- src/discof/execrp/test_execrp_tile.c | 4 +- src/discof/replay/test_replay_tile.c | 9 +- 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/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 ++++-- 44 files changed, 1537 insertions(+), 287 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/disco/gui/fd_gui.c b/src/disco/gui/fd_gui.c index a3aa58dc5da..6b8cd35a4ba 100644 --- a/src/disco/gui/fd_gui.c +++ b/src/disco/gui/fd_gui.c @@ -2365,7 +2365,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..30f5acc83d0 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,79 @@ 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 ); + ulong ec_skip = fd_ulong_sat_sub( ec_cnt, FD_VOTE_STATE_V5_ACCUMULATED_LAMPORTS_MAX ); + 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-ec_skip ) + }; + memcpy( v5.bls_pubkey_compressed, ele->bls_key, FD_BLS_PUB_COMPRESSED_SZ ); + for( ulong k=0UL, j=0UL; k=ec_skip ) v5.accumulated_lamports[ k-ec_skip ] = (fd_vote_epoch_credits_t){ ULONG_MAX, ULONG_MAX, ULONG_MAX }; + continue; + } + if( k>=ec_skip ) v5.accumulated_lamports[ k-ec_skip ] = (fd_vote_epoch_credits_t){ (ulong)ec->epoch[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 41a8fc8b3bd..02a2531dcde 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; @@ -812,6 +829,10 @@ test_manifest_v5_vote_accounts( fd_svm_mini_t * mini, fd_snapshot_manifest_vote_stakes_t want = a->epoch_stakes[ i ].vote_stakes[ j ]; FD_TEST( vs.has_identity_bls==1 ); vs.has_identity_bls = want.has_identity_bls; + fd_validator_location_t const la = want.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; ulong skip = fd_ulong_sat_sub( want.epoch_credits_history_len, FD_VOTE_STATE_V5_ACCUMULATED_LAMPORTS_MAX ); want.epoch_credits_history_len -= skip; memmove( want.epoch_credits, want.epoch_credits+skip, want.epoch_credits_history_len*sizeof(epoch_credits_t) ); diff --git a/src/discof/execrp/test_execrp_tile.c b/src/discof/execrp/test_execrp_tile.c index d75c7acc67a..f6fb41eff76 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 3e3f7067c9d..a6d0dddd71f 100644 --- a/src/discof/replay/test_replay_tile.c +++ b/src/discof/replay/test_replay_tile.c @@ -29,6 +29,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; @@ -418,6 +420,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; @@ -793,7 +796,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 ); @@ -1640,7 +1643,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 ); @@ -1998,7 +2001,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. */ @@ -532,7 +532,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 ); @@ -544,7 +544,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 8ec66c87f39..3e439354b6b 100644 --- a/src/discof/restore/utils/fd_ssmanifest_parser.c +++ b/src/discof/restore/utils/fd_ssmanifest_parser.c @@ -1416,6 +1416,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; @@ -1518,7 +1519,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 && @@ -1565,7 +1569,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 29540fc7dcf..8127dc8182b 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 */ @@ -68,6 +69,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 0538d6fb88c..356f555b3e0 100644 --- a/src/discof/restore/utils/test_ssload.c +++ b/src/discof/restore/utils/test_ssload.c @@ -820,8 +820,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 ); @@ -838,6 +851,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). */ @@ -921,6 +937,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 ); @@ -933,6 +950,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 e93f15d7179..0e96e1443c7 100644 --- a/src/discof/rpc/test_rpc_tile.c +++ b/src/discof/rpc/test_rpc_tile.c @@ -230,6 +230,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/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" )); } @@ -1719,7 +1721,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 0751ad76f84..38588bdcd9c 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 67c9f067393..a7e526dd44c 100644 --- a/src/flamenco/runtime/program/vote/fd_vote_codec.h +++ b/src/flamenco/runtime/program/vote/fd_vote_codec.h @@ -796,6 +796,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