From 0df955e323db2d274ff64eabc40ab028bcc04ba2 Mon Sep 17 00:00:00 2001 From: Tom Pointon Date: Fri, 16 Oct 2026 04:20:22 +0000 Subject: [PATCH] flamenco, runtime: enforce validator location registration --- src/flamenco/features/fd_features_generated.c | 9 ++ src/flamenco/features/fd_features_generated.h | 5 +- src/flamenco/features/feature_map.json | 3 +- .../runtime/test_vat_refresh_vote_accounts.c | 99 ++++++++++++++++++- src/flamenco/stakes/fd_stakes.c | 4 +- 5 files changed, 115 insertions(+), 5 deletions(-) diff --git a/src/flamenco/features/fd_features_generated.c b/src/flamenco/features/fd_features_generated.c index 23aaaee556e..9e4f6264e7c 100644 --- a/src/flamenco/features/fd_features_generated.c +++ b/src/flamenco/features/fd_features_generated.c @@ -2365,6 +2365,13 @@ fd_feature_id_t const ids[] = { .implemented = 1, .cleaned_up = 0 }, + { .index = offsetof(fd_features_t, enforce_validator_location_registration)>>3, + .id = {"\x03\x88\x30\x50\xef\xa6\x06\xee\xcd\x5f\xba\x36\xf7\x57\xd7\xee\xb3\x71\x16\x40\xb2\x21\xc3\xc1\x29\x3e\x1e\x58\x55\x11\xaa\x80"}, + /* EnforceVa1idatorLocat1onRegistrat1on1111111 */ + .name = "enforce_validator_location_registration", + .implemented = 1, + .cleaned_up = 0 }, + { .index = ULONG_MAX } }; @@ -2683,6 +2690,7 @@ typedef struct fd_feature_id_lookup_entry fd_feature_id_lookup_entry_t; #define MAP_PERFECT_299 0x7b717607817b2451UL, .val = &ids[299] #define MAP_PERFECT_300 0x0aa7b29de47f96cbUL, .val = &ids[300] #define MAP_PERFECT_301 0xdbf2487401975107UL, .val = &ids[301] +#define MAP_PERFECT_302 0xee06a6ef50308803UL, .val = &ids[302] #include "../../util/tmpl/fd_map_perfect.c" @@ -2995,4 +3003,5 @@ FD_STATIC_ASSERT( offsetof( fd_features_t, vote_account_initialize_v2 FD_STATIC_ASSERT( offsetof( fd_features_t, raise_cpi_nesting_limit_to_8 )>>3==299UL, layout ); 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( 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 8b86f856515..50b44a1fbc6 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 (302UL) +#define FD_FEATURE_ID_CNT (303UL) /* Feature set ID calculated from all feature names */ -#define FD_FEATURE_SET_ID (3960938763U) +#define FD_FEATURE_SET_ID (3342991122U) union fd_features { ulong f[ FD_FEATURE_ID_CNT ]; @@ -318,5 +318,6 @@ union fd_features { /* 0x7b717607817b2451 */ ulong raise_cpi_nesting_limit_to_8; /* 0x0aa7b29de47f96cb */ ulong loader_v3_set_program_data_to_elf_length; /* 0xdbf2487401975107 */ ulong validator_location_registration; + /* 0xee06a6ef50308803 */ ulong enforce_validator_location_registration; }; }; diff --git a/src/flamenco/features/feature_map.json b/src/flamenco/features/feature_map.json index 43b67e11897..76bb282a654 100644 --- a/src/flamenco/features/feature_map.json +++ b/src/flamenco/features/feature_map.json @@ -300,5 +300,6 @@ {"name":"vote_account_initialize_v2","implemented":0,"pubkey":"VoteAccount1nitia1izeV211111111111111111111"}, {"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":"validator_location_registration","implemented":1,"pubkey":"Va1idatorLocat1onRegistrat1on11111111111111","comment":"SIMD-0674, placeholder pubkey"}, + {"name":"enforce_validator_location_registration","implemented":1,"pubkey":"EnforceVa1idatorLocat1onRegistrat1on1111111","comment":"placeholder pubkey"} ] diff --git a/src/flamenco/runtime/test_vat_refresh_vote_accounts.c b/src/flamenco/runtime/test_vat_refresh_vote_accounts.c index cb20e248a7c..a506900062c 100644 --- a/src/flamenco/runtime/test_vat_refresh_vote_accounts.c +++ b/src/flamenco/runtime/test_vat_refresh_vote_accounts.c @@ -19,6 +19,7 @@ #include "../features/fd_features.h" #include "../stakes/fd_stake_types.h" #include "../stakes/fd_vote_stakes.h" +#include "../stakes/fd_stakes.h" #include #include @@ -27,6 +28,7 @@ #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 * voter0_location = &LOC_FRA; #define TEST_SLOTS_PER_EPOCH (12UL) #define TEST_VAT_EPOCH (3UL) @@ -187,12 +189,14 @@ put_vote_account_v4( test_env_t * env, voter->prio = node_pubkey->uc[0]; fd_vote_authorized_voters_treap_ele_insert( vs->authorized_voters.treap, voter, vs->authorized_voters.pool ); - if( voter0_is_v5 && fd_pubkey_eq( vote_account, &(fd_pubkey_t){ .ul[ 0 ] = 0x100UL } ) ) { + int v5 = voter0_is_v5 && fd_pubkey_eq( vote_account, &(fd_pubkey_t){ .ul[ 0 ] = 0x100UL } ); + if( v5 ) { FD_TEST( !fd_vsv_try_convert_to_v5( versioned, env->bank->f.epoch ) ); versioned->v5.location = LOC_FRA; } FD_TEST( !fd_vote_state_versioned_serialize( versioned, vote_state_data, sizeof(vote_state_data) ) ); + if( v5 ) fd_vote_account_v5( vote_state_data )->location = *voter0_location; fd_acc_t acc = fd_accdb_write_one( env->accdb, env->bank->accdb_fork_id, vote_account->uc ); acc.lamports = lamports; @@ -270,6 +274,24 @@ enable_feature( test_env_t * env, ulong byte_offset, ulong activation_slot ) { FD_LOG_ERR(( "feature id not found" )); } +static void +enable_feature_pending( test_env_t * env, ulong byte_offset ) { + for( fd_feature_id_t const * id = fd_feature_iter_init(); !fd_feature_iter_done( id ); id = fd_feature_iter_next( id ) ) { + if( id->index != (byte_offset>>3) ) continue; + fd_feature_t feature = { .is_active = 0, .activation_slot = 0UL }; + fd_acc_t acc = fd_accdb_write_one( env->accdb, env->bank->accdb_fork_id, id->id.uc ); + acc.lamports = 1UL; + acc.executable = 0; + fd_memcpy( acc.owner, fd_solana_feature_program_id.key, sizeof(fd_pubkey_t) ); + acc.data_len = sizeof(fd_feature_t); + fd_memcpy( acc.data, &feature, sizeof(fd_feature_t) ); + acc.commit = 1; + fd_accdb_unwrite_one( env->accdb, &acc ); + return; + } + FD_LOG_ERR(( "feature id not found" )); +} + /* Prep state for the upcoming epoch `for_epoch`, written into the current (still un-rooted) slot so the next boundary refresh sees it: - rewrite each voter's vote account (cached last vote -> first slot @@ -621,6 +643,78 @@ test_vat_path_v5_voter( fd_wksp_t * wksp ) { voter0_is_v5 = 0; } +static void +test_enforce_registration_from_genesis( 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 ); + + ulong const epoch = TEST_VAT_EPOCH-1UL; + advance_into_epoch( env, epoch ); + + FD_TEST( vote_stakes_t1_cnt( env->bank )==1UL ); + for( ulong i=0UL; ibank ), env->bank->vote_stakes_fork_id, &v, NULL )==( i==0UL ) ); + } + FD_TEST( env->bank->f.total_epoch_stake==stake_for_epoch( 0UL, epoch ) ); + FD_TEST( clock_delta( env )==CLOCK_DELTA_NO_WHALE ); + + test_env_destroy( env ); + voter0_is_v5 = 0; +} + +static void +test_enforce_registration_activates_at_boundary( fd_wksp_t * wksp ) { + voter0_is_v5 = 1; + test_env_t env[1]; + test_env_create( env, wksp ); + + fd_pubkey_t v0 = vote_key( 0UL ); + fd_pubkey_t v1 = vote_key( 1UL ); + + advance_into_epoch( env, 1UL ); + FD_TEST( !FD_FEATURE_ACTIVE_BANK( env->bank, enforce_validator_location_registration ) ); + FD_TEST( vote_stakes_t1_cnt( env->bank )==NUM_VAT_ELIGIBLE ); + FD_TEST( fd_vote_stakes_query_t_1( fd_bank_vote_stakes( env->bank ), env->bank->vote_stakes_fork_id, &v1, NULL ) ); + + enable_feature_pending( env, offsetof( fd_features_t, enforce_validator_location_registration ) ); + advance_into_epoch( env, 2UL ); + + FD_TEST( FD_FEATURE_ACTIVE_BANK( env->bank, enforce_validator_location_registration ) ); + FD_TEST( env->bank->f.features.enforce_validator_location_registration==2UL*TEST_SLOTS_PER_EPOCH ); + FD_TEST( vote_stakes_t1_cnt( env->bank )==1UL ); + FD_TEST( fd_vote_stakes_query_t_1( fd_bank_vote_stakes( env->bank ), env->bank->vote_stakes_fork_id, &v0, NULL ) ); + FD_TEST( !fd_vote_stakes_query_t_1( fd_bank_vote_stakes( env->bank ), env->bank->vote_stakes_fork_id, &v1, NULL ) ); + FD_TEST( fd_vote_stakes_query_t_2( fd_bank_vote_stakes( env->bank ), env->bank->vote_stakes_fork_id, &v1, NULL ) ); + FD_TEST( env->bank->f.total_epoch_stake==stake_for_epoch( 0UL, 2UL ) ); + + test_env_destroy( env ); + voter0_is_v5 = 0; +} + +static void +test_enforce_registration_v5_invalid_location( fd_wksp_t * wksp ) { + static fd_validator_location_t const in_bounds_invalid = { 6000000, 0, 0 }; + voter0_is_v5 = 1; + voter0_location = &in_bounds_invalid; + test_env_t env[1]; + test_env_create( env, wksp ); + + fd_pubkey_t v0 = vote_key( 0UL ); + fd_acc_t ro = fd_accdb_read_one( env->accdb, env->bank->accdb_fork_id, v0.uc ); + FD_TEST( FD_LOAD( uint, ro.data )==fd_vote_state_versioned_enum_v5 && FD_LOAD( int, ro.data+132 )==6000000 ); + FD_TEST( fd_stakes_vote_account_is_admissible( env->bank, ro.lamports, ro.owner, ro.data, ro.data_len ) ); + FD_FEATURE_SET_ACTIVE( &env->bank->f.features, enforce_validator_location_registration, 0UL ); + FD_TEST( !fd_stakes_vote_account_is_admissible( env->bank, ro.lamports, ro.owner, ro.data, ro.data_len ) ); + fd_accdb_unread_one( env->accdb, &ro ); + + test_env_destroy( env ); + voter0_location = &LOC_FRA; + voter0_is_v5 = 0; +} + /* 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. */ @@ -731,6 +825,9 @@ main( int argc, char ** argv ) { test_runtime_stack_stake_accum_map_sizing( wksp ); test_vat_path_unconditional( wksp ); test_vat_path_v5_voter( wksp ); + test_enforce_registration_from_genesis( wksp ); + test_enforce_registration_activates_at_boundary( wksp ); + test_enforce_registration_v5_invalid_location( wksp ); test_vat_invalidate_revalidate( wksp ); fd_wksp_delete_anonymous( wksp ); diff --git a/src/flamenco/stakes/fd_stakes.c b/src/flamenco/stakes/fd_stakes.c index 7860e6c1b31..b3859baed87 100644 --- a/src/flamenco/stakes/fd_stakes.c +++ b/src/flamenco/stakes/fd_stakes.c @@ -544,7 +544,9 @@ fd_stakes_vote_account_is_admissible( fd_bank_t const * bank, ulong minimum_balance = fd_rent_exempt_minimum_balance( &bank->f.rent, FD_VOTE_STATE_V4_SZ ) + vat_to_burn_per_epoch; return lamports>=minimum_balance && fd_vsv_is_correct_size_owner_and_init( owner, data, data_len ) && - fd_vote_account_has_bls_pubkey( data, data_len ); + fd_vote_account_has_bls_pubkey( data, data_len ) && + !( FD_FEATURE_ACTIVE_BANK( bank, enforce_validator_location_registration ) && + !fd_vote_account_is_valid_v5( data, data_len ) ); } void