diff --git a/src/flamenco/runtime/context/fd_exec_slot_ctx.c b/src/flamenco/runtime/context/fd_exec_slot_ctx.c index 454bc90e1f..a5771f19a1 100644 --- a/src/flamenco/runtime/context/fd_exec_slot_ctx.c +++ b/src/flamenco/runtime/context/fd_exec_slot_ctx.c @@ -134,6 +134,102 @@ fd_exec_slot_ctx_recover( fd_exec_slot_ctx_t * slot_ctx, fd_vote_accounts_vote_accounts_root_update( curr_epoch_stakes, curr_epoch_stakes_root ); fd_bank_curr_epoch_stakes_end_locking_modify( slot_ctx->bank ); + fd_vote_states_t * vote_states = fd_vote_states_join( fd_vote_states_new( fd_bank_vote_states_locking_modify( slot_ctx->bank ), FD_RUNTIME_MAX_VOTE_ACCOUNTS ) ); + if( FD_UNLIKELY( !vote_states ) ) { + FD_LOG_CRIT(( "unable to join vote states" )); + } + + for( fd_vote_accounts_pair_global_t_mapnode_t * n = fd_vote_accounts_pair_global_t_map_minimum( manifest_vote_accounts_pool, manifest_vote_accounts_root ); + n; + n = fd_vote_accounts_pair_global_t_map_successor( manifest_vote_accounts_pool, n ) ) { + + ulong data_len = n->elem.value.data_len; + uchar * manifest_data = fd_solana_account_data_join( &n->elem.value ); + + uchar data[5000UL]; + fd_bincode_decode_static( + vote_state_versioned, + data, + manifest_data, + data_len, + &decode_err ); + + fd_vote_state_versioned_t * vsv = (fd_vote_state_versioned_t *)data; + + uchar comission; + ulong credits_cnt = 0UL; + ushort epoch[EPOCH_CREDITS_MAX]; + ulong credits[EPOCH_CREDITS_MAX]; + ulong prev_credits[EPOCH_CREDITS_MAX]; + + fd_vote_epoch_credits_t * epoch_credits = NULL; + + + switch( vsv->discriminant ) { + case fd_vote_state_versioned_enum_v0_23_5: + comission = vsv->inner.v0_23_5.commission; + + epoch_credits = vsv->inner.v0_23_5.epoch_credits; + + for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits ); + !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter ); + iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) { + + fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( epoch_credits, iter ); + + epoch[credits_cnt] = (ushort)ele->epoch; + credits[credits_cnt] = ele->credits; + prev_credits[credits_cnt] = ele->prev_credits; + credits_cnt++; + } + + break; + case fd_vote_state_versioned_enum_v1_14_11: + comission = vsv->inner.v1_14_11.commission; + + epoch_credits = vsv->inner.v1_14_11.epoch_credits; + + for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits ); + !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter ); + iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) { + + fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( epoch_credits, iter ); + + epoch[credits_cnt] = (ushort)ele->epoch; + credits[credits_cnt] = ele->credits; + prev_credits[credits_cnt] = ele->prev_credits; + credits_cnt++; + } + break; + case fd_vote_state_versioned_enum_current: + comission = vsv->inner.current.commission; + epoch_credits = vsv->inner.current.epoch_credits; + + for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits ); + !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter ); + iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) { + + fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( epoch_credits, iter ); + + epoch[credits_cnt] = (ushort)ele->epoch; + credits[credits_cnt] = ele->credits; + prev_credits[credits_cnt] = ele->prev_credits; + credits_cnt++; + } + break; + default: + __builtin_unreachable(); + } + + fd_vote_states_update( vote_states, &n->elem.key, comission, n->elem.stake, credits_cnt, epoch, credits, prev_credits ); + + } + + + fd_bank_vote_states_end_locking_modify( slot_ctx->bank ); + + + fd_bank_epoch_set( slot_ctx->bank, manifest->bank.epoch ); /* Move EpochStakes */ diff --git a/src/flamenco/runtime/fd_bank.h b/src/flamenco/runtime/fd_bank.h index a772e4be3c..f2900c43d4 100644 --- a/src/flamenco/runtime/fd_bank.h +++ b/src/flamenco/runtime/fd_bank.h @@ -6,6 +6,7 @@ #include "../features/fd_features.h" #include "../rewards/fd_epoch_rewards.h" #include "../stakes/fd_stake_delegations.h" +#include "../stakes/fd_vote_states.h" #include "../fd_rwlock.h" #include "fd_runtime_const.h" #include "fd_blockhashes.h" @@ -180,6 +181,7 @@ FD_PROTOTYPES_BEGIN X(fd_rent_t, rent, sizeof(fd_rent_t), alignof(fd_rent_t), 0, 0, 0 ) /* Rent */ \ X(fd_slot_lthash_t, lthash, sizeof(fd_slot_lthash_t), alignof(fd_slot_lthash_t), 0, 0, 1 ) /* LTHash */ \ X(fd_sysvar_cache_t, sysvar_cache, sizeof(fd_sysvar_cache_t), alignof(fd_sysvar_cache_t), 0, 0, 0 ) /* Sysvar cache */ \ + X(fd_vote_states_t, vote_states, FD_VOTE_STATES_FOOTPRINT, FD_VOTE_STATES_ALIGN, 1, 0, 1 ) /* Vote states */ \ X(fd_vote_accounts_global_t, next_epoch_stakes, 200000000UL, 128UL, 1, 0, 1 ) /* Next epoch stakes, ~4K per account * 50k vote accounts */ \ /* These are the stakes that determine the leader */ \ /* schedule for the upcoming epoch. If we are executing */ \ @@ -286,6 +288,12 @@ FD_PROTOTYPES_BEGIN #undef POOL_NAME #undef POOL_T +#define POOL_NAME fd_bank_vote_states_pool +#define POOL_T fd_bank_vote_states_t +#include "../../util/tmpl/fd_pool.c" +#undef POOL_NAME +#undef POOL_T + /* As mentioned above, the overall layout of the bank struct: - Fields used for internal pool/bank management - Non-Cow fields diff --git a/src/flamenco/runtime/program/fd_vote_program.c b/src/flamenco/runtime/program/fd_vote_program.c index 8c24eb703d..faf3f31380 100644 --- a/src/flamenco/runtime/program/fd_vote_program.c +++ b/src/flamenco/runtime/program/fd_vote_program.c @@ -2845,96 +2845,110 @@ static void remove_vote_account( fd_txn_account_t * vote_account, fd_bank_t * bank ) { - fd_vote_accounts_global_t * epoch_vote_accounts = fd_bank_curr_epoch_stakes_locking_modify( bank ); - fd_vote_accounts_pair_global_t_mapnode_t * epoch_vote_accounts_pool = fd_vote_accounts_vote_accounts_pool_join( epoch_vote_accounts ); - fd_vote_accounts_pair_global_t_mapnode_t * epoch_vote_accounts_root = fd_vote_accounts_vote_accounts_root_join( epoch_vote_accounts ); + fd_vote_states_t * vote_states = fd_bank_vote_states_locking_modify( bank ); - if( FD_UNLIKELY( epoch_vote_accounts_pool==NULL ) ) { - FD_LOG_DEBUG(("Vote accounts pool does not exist")); - fd_bank_curr_epoch_stakes_end_locking_modify( bank ); - return; - } + fd_vote_states_remove( vote_states, vote_account->pubkey ); + fd_bank_vote_states_end_locking_modify( bank ); +} - fd_vote_accounts_pair_global_t_mapnode_t vote_acc; - fd_memcpy( vote_acc.elem.key.uc, vote_account->pubkey->uc, sizeof(fd_pubkey_t) ); - fd_vote_accounts_pair_global_t_mapnode_t * vote_account_entry = fd_vote_accounts_pair_global_t_map_find( epoch_vote_accounts_pool, epoch_vote_accounts_root, &vote_acc ); - if( FD_LIKELY( vote_account_entry ) ) { - fd_vote_accounts_pair_global_t_map_remove( epoch_vote_accounts_pool, &epoch_vote_accounts_root, vote_account_entry); - } +static void +upsert_vote_account( fd_txn_account_t * vote_account, + fd_bank_t * bank ) { - fd_vote_accounts_vote_accounts_pool_update( epoch_vote_accounts, epoch_vote_accounts_pool ); - fd_vote_accounts_vote_accounts_root_update( epoch_vote_accounts, epoch_vote_accounts_root ); - fd_bank_curr_epoch_stakes_end_locking_modify( bank ); + fd_vote_states_t * vote_states = fd_bank_vote_states_locking_modify( bank ); - fd_account_keys_global_t * vote_account_keys = fd_bank_vote_account_keys_locking_modify( bank ); - fd_account_keys_pair_t_mapnode_t * vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys ); - fd_account_keys_pair_t_mapnode_t * vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys ); + if( fd_vote_state_versions_is_correct_and_initialized( vote_account ) ) { + uchar * acc_data = fd_txn_account_get_data( vote_account ); + ulong data_len = fd_txn_account_get_data_len( vote_account ); - if( FD_UNLIKELY( vote_account_keys_pool==NULL ) ) { - fd_bank_vote_account_keys_end_locking_modify( bank ); - FD_LOG_DEBUG(("Vote accounts pool does not exist")); - return; - } + uchar data[5000UL]; + fd_bincode_decode_static( + vote_state_versioned, + data, + acc_data, + data_len, + &decode_err ); - fd_account_keys_pair_t_mapnode_t account_key; - fd_memcpy( account_key.elem.key.uc, vote_account->pubkey->uc, sizeof(fd_pubkey_t) ); - fd_account_keys_pair_t_mapnode_t * account_key_entry = fd_account_keys_pair_t_map_find( vote_account_keys_pool, vote_account_keys_root, &account_key ); - if( account_key_entry ) { - fd_account_keys_pair_t_map_remove( vote_account_keys_pool, &vote_account_keys_root, account_key_entry ); - } + fd_vote_state_versioned_t * vsv = (fd_vote_state_versioned_t *)data; - fd_account_keys_account_keys_pool_update( vote_account_keys, vote_account_keys_pool ); + uchar comission; + ulong credits_cnt = 0UL; + ushort epoch[EPOCH_CREDITS_MAX]; + ulong credits[EPOCH_CREDITS_MAX]; + ulong prev_credits[EPOCH_CREDITS_MAX]; - fd_bank_vote_account_keys_end_locking_modify( bank ); -} + fd_vote_epoch_credits_t * epoch_credits = NULL; -static void -upsert_vote_account( fd_txn_account_t * vote_account, - fd_bank_t * bank ) { + switch( vsv->discriminant ) { + case fd_vote_state_versioned_enum_v0_23_5: + comission = vsv->inner.v0_23_5.commission; - fd_vote_accounts_global_t const * vote_accounts = fd_bank_curr_epoch_stakes_locking_query( bank ); - fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_pool = fd_vote_accounts_vote_accounts_pool_join( vote_accounts ); - fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_root = fd_vote_accounts_vote_accounts_root_join( vote_accounts ); + epoch_credits = vsv->inner.v0_23_5.epoch_credits; - fd_account_keys_global_t * vote_account_keys = fd_bank_vote_account_keys_locking_modify( bank ); - fd_account_keys_pair_t_mapnode_t * vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys ); - fd_account_keys_pair_t_mapnode_t * vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys ); + for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits ); + !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter ); + iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) { - if( FD_UNLIKELY( vote_account_keys_pool==NULL ) ) { - fd_bank_vote_account_keys_end_locking_modify( bank ); - fd_bank_curr_epoch_stakes_end_locking_query( bank ); - FD_LOG_DEBUG(( "Vote accounts pool does not exist" )); - return; - } + fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( epoch_credits, iter ); - if( fd_vote_state_versions_is_correct_and_initialized( vote_account ) ) { - fd_account_keys_pair_t_mapnode_t key; - fd_memcpy( &key.elem.key, vote_account->pubkey->uc, sizeof(fd_pubkey_t) ); + epoch[credits_cnt] = (ushort)ele->epoch; + credits[credits_cnt] = ele->credits; + prev_credits[credits_cnt] = ele->prev_credits; + credits_cnt++; + } - fd_vote_accounts_pair_global_t_mapnode_t vote_acc; - fd_memcpy( &vote_acc.elem.key, vote_account->pubkey->uc, sizeof(fd_pubkey_t) ); + break; + case fd_vote_state_versioned_enum_v1_14_11: + comission = vsv->inner.v1_14_11.commission; - // Skip duplicates - if( FD_LIKELY( fd_account_keys_pair_t_map_find( vote_account_keys_pool, vote_account_keys_root, &key ) || - fd_vote_accounts_pair_global_t_map_find( stakes_vote_accounts_pool, stakes_vote_accounts_root, &vote_acc ) ) ) { - fd_bank_vote_account_keys_end_locking_modify( bank ); - fd_bank_curr_epoch_stakes_end_locking_query( bank ); - return; - } - fd_bank_curr_epoch_stakes_end_locking_query( bank ); + epoch_credits = vsv->inner.v1_14_11.epoch_credits; + + for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits ); + !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter ); + iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) { + + fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( epoch_credits, iter ); + + epoch[credits_cnt] = (ushort)ele->epoch; + credits[credits_cnt] = ele->credits; + prev_credits[credits_cnt] = ele->prev_credits; + credits_cnt++; + } + break; + case fd_vote_state_versioned_enum_current: + comission = vsv->inner.current.commission; + epoch_credits = vsv->inner.current.epoch_credits; - fd_account_keys_pair_t_mapnode_t * new_node = fd_account_keys_pair_t_map_acquire( vote_account_keys_pool ); - if( FD_UNLIKELY( !new_node ) ) { - FD_LOG_ERR(("Map full")); + for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits ); + !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter ); + iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) { + + fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( epoch_credits, iter ); + + epoch[credits_cnt] = (ushort)ele->epoch; + credits[credits_cnt] = ele->credits; + prev_credits[credits_cnt] = ele->prev_credits; + credits_cnt++; + } + break; + default: + __builtin_unreachable(); } - fd_memcpy( &new_node->elem.key, vote_account->pubkey, sizeof(fd_pubkey_t)); - fd_account_keys_pair_t_map_insert( vote_account_keys_pool, &vote_account_keys_root, new_node ); - fd_bank_vote_account_keys_end_locking_modify( bank ); + fd_vote_states_update( + vote_states, + vote_account->pubkey, + comission, + 0UL, + credits_cnt, + epoch, + credits, + prev_credits ); + + fd_bank_vote_states_end_locking_modify( bank ); + } else { - fd_bank_vote_account_keys_end_locking_modify( bank ); - fd_bank_curr_epoch_stakes_end_locking_query( bank ); remove_vote_account( vote_account, bank ); } } diff --git a/src/flamenco/stakes/Local.mk b/src/flamenco/stakes/Local.mk index 8edfe17811..71f72f0502 100644 --- a/src/flamenco/stakes/Local.mk +++ b/src/flamenco/stakes/Local.mk @@ -7,6 +7,12 @@ $(call add-objs,fd_stake_delegations,fd_flamenco) $(call make-unit-test,test_stake_delegations,test_stake_delegations,fd_flamenco fd_ballet fd_util) $(call run-unit-test,test_stake_delegations) +$(call add-hdrs,fd_vote_states.h) +$(call add-objs,fd_vote_states,fd_flamenco) +$(call make-unit-test,test_vote_states,test_vote_states,fd_flamenco fd_ballet fd_util) +$(call run-unit-test,test_vote_states) + + # TODO this should not depend on fd_funk ifdef FD_HAS_HOSTED $(call make-bin,fd_stakes_from_snapshot,fd_stakes_from_snapshot,fd_flamenco fd_funk fd_ballet fd_util) diff --git a/src/flamenco/stakes/fd_stake_delegations.c b/src/flamenco/stakes/fd_stake_delegations.c index fb4320b335..9698d926da 100644 --- a/src/flamenco/stakes/fd_stake_delegations.c +++ b/src/flamenco/stakes/fd_stake_delegations.c @@ -22,7 +22,7 @@ ulong fd_stake_delegations_align( void ) { /* The align of the struct should be the max of the align of the data structures that it contains. In this case, this is the map, the - pool, and the struct itself*/ + pool, and the struct itself */ return fd_ulong_max( fd_ulong_max( fd_stake_delegation_map_align(), fd_stake_delegation_pool_align() ), alignof(fd_stake_delegations_t) ); } diff --git a/src/flamenco/stakes/fd_stakes.c b/src/flamenco/stakes/fd_stakes.c index 22e236bde4..a7427eb1b8 100644 --- a/src/flamenco/stakes/fd_stakes.c +++ b/src/flamenco/stakes/fd_stakes.c @@ -144,10 +144,8 @@ fd_populate_vote_accounts( fd_exec_slot_ctx_t * slot_ctx, fd_account_keys_pair_t_mapnode_t * vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys ); ulong vote_account_keys_map_sz = vote_account_keys_pool ? fd_account_keys_pair_t_map_size( vote_account_keys_pool, vote_account_keys_root ) : 0UL; - fd_vote_accounts_global_t const * vote_accounts = fd_bank_curr_epoch_stakes_locking_query( slot_ctx->bank ); - fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_pool = fd_vote_accounts_vote_accounts_pool_join( vote_accounts ); - fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_root = fd_vote_accounts_vote_accounts_root_join( vote_accounts ); - ulong vote_accounts_stakes_map_sz = vote_accounts_pool ? fd_vote_accounts_pair_global_t_map_size( vote_accounts_pool, vote_accounts_root ) : 0UL; + fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_query( slot_ctx->bank ); + ulong vote_accounts_stakes_map_sz = fd_vote_states_count( vote_states ); ulong vote_states_pool_sz = vote_accounts_stakes_map_sz + vote_account_keys_map_sz; temp_info->vote_states_root = NULL; @@ -161,16 +159,22 @@ fd_populate_vote_accounts( fd_exec_slot_ctx_t * slot_ctx, /* We can optimize this function by only iterating over the vote accounts (since there's much fewer of them) instead of all of the stake accounts, and pre-inserting them into the delegations pool. This way, the delegation calculations can be tpooled. */ - for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( vote_accounts_pool, vote_accounts_root ); - elem; - elem = fd_vote_accounts_pair_global_t_map_successor( vote_accounts_pool, elem ) ) { + fd_vote_state_map_t * vote_state_map = fd_vote_states_get_map( vote_states ); + fd_vote_state_ele_t * vote_state_pool = fd_vote_states_get_pool( vote_states ); + + for( fd_vote_state_map_iter_t iter = fd_vote_state_map_iter_init( vote_state_map, vote_state_pool ); + !fd_vote_state_map_iter_done( iter, vote_state_map, vote_state_pool ); + iter = fd_vote_state_map_iter_next( iter, vote_state_map, vote_state_pool ) ) { + + fd_vote_state_ele_t const * vote_state = fd_vote_state_map_iter_ele_const( iter, vote_state_map, vote_state_pool ); + fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_acquire( pool ); - entry->elem.key = elem->elem.key; - entry->elem.stake = 0UL; + entry->elem.key = vote_state->vote_account; + entry->elem.stake = vote_state->stake; fd_stake_weight_t_map_insert( pool, &root, entry ); } - fd_bank_curr_epoch_stakes_end_locking_query( slot_ctx->bank ); + fd_bank_vote_states_end_locking_query( slot_ctx->bank ); for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( vote_account_keys_pool, vote_account_keys_root ); n; @@ -260,9 +264,7 @@ fd_refresh_vote_accounts( fd_exec_slot_ctx_t * slot_ctx, fd_epoch_info_t * temp_info, fd_spad_t * runtime_spad ) { - fd_vote_accounts_global_t * vote_accounts = fd_bank_curr_epoch_stakes_locking_modify( slot_ctx->bank ); - fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_pool = fd_vote_accounts_vote_accounts_pool_join( vote_accounts ); - fd_vote_accounts_pair_global_t_mapnode_t * stakes_vote_accounts_root = fd_vote_accounts_vote_accounts_root_join( vote_accounts ); + fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_modify( slot_ctx->bank ); fd_account_keys_global_t * vote_account_keys = fd_bank_vote_account_keys_locking_modify( slot_ctx->bank ); fd_account_keys_pair_t_mapnode_t * vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys ); @@ -282,17 +284,6 @@ fd_refresh_vote_accounts( fd_exec_slot_ctx_t * slot_ctx, fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join( fd_stake_weight_t_map_new( mem, vote_states_pool_sz ) ); fd_stake_weight_t_mapnode_t * root = NULL; - /* We can optimize this function by only iterating over the vote accounts (since there's much fewer of them) instead of all - of the stake accounts, and pre-inserting them into the delegations pool. This way, the delegation calculations can be tpooled. */ - for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( stakes_vote_accounts_pool, stakes_vote_accounts_root ); - elem; - elem = fd_vote_accounts_pair_global_t_map_successor( stakes_vote_accounts_pool, elem ) ) { - fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_acquire( pool ); - entry->elem.key = elem->elem.key; - entry->elem.stake = 0UL; - fd_stake_weight_t_map_insert( pool, &root, entry ); - } - for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( vote_account_keys_pool, vote_account_keys_root ); n; n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) { @@ -319,87 +310,42 @@ fd_refresh_vote_accounts( fd_exec_slot_ctx_t * slot_ctx, temp_info->stake_infos_len ); - // Iterate over each vote account in the epoch stakes cache and populate the new vote accounts pool ulong total_epoch_stake = 0UL; - for( fd_vote_accounts_pair_global_t_mapnode_t * elem = fd_vote_accounts_pair_global_t_map_minimum( stakes_vote_accounts_pool, stakes_vote_accounts_root ); - elem; - elem = fd_vote_accounts_pair_global_t_map_successor( stakes_vote_accounts_pool, elem ) ) { - - fd_pubkey_t const * vote_account_pubkey = &elem->elem.key; - fd_vote_state_versioned_t * vote_state = deserialize_and_update_vote_account( slot_ctx, - elem, - root, - pool, - vote_account_pubkey, - runtime_spad ); - if( FD_LIKELY( vote_state ) ) { - total_epoch_stake += elem->elem.stake; - // Insert into the temporary vote states cache - /* FIXME: This copy copies over some local pointers, which means - that the allocation done when deserializing the vote account - is not freed until the end of the epoch boundary processing. */ - fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool ); - new_vote_state_node->elem.account = *vote_account_pubkey; - new_vote_state_node->elem.state = *vote_state; - fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node ); - } else { - FD_LOG_WARNING(( "Failed to deserialize vote account" )); - } - } - - // Update the epoch stakes cache with new vote accounts from the epoch - for( fd_account_keys_pair_t_mapnode_t * n = fd_account_keys_pair_t_map_minimum( vote_account_keys_pool, vote_account_keys_root ); - n; - n = fd_account_keys_pair_t_map_successor( vote_account_keys_pool, n ) ) { - - fd_pubkey_t const * vote_account_pubkey = &n->elem.key; - fd_vote_accounts_pair_global_t_mapnode_t key; - key.elem.key = *vote_account_pubkey; - - /* No need to process duplicate vote account keys. This is a mostly redundant check - since upserting vote accounts also checks against the vote stakes, but this is - there anyways in case that ever changes */ - if( FD_UNLIKELY( fd_vote_accounts_pair_global_t_map_find( stakes_vote_accounts_pool, stakes_vote_accounts_root, &key ) ) ) { - continue; - } - - fd_vote_accounts_pair_global_t_mapnode_t * new_vote_node = fd_vote_accounts_pair_global_t_map_acquire( stakes_vote_accounts_pool ); - fd_vote_state_versioned_t * vote_state = deserialize_and_update_vote_account( slot_ctx, - new_vote_node, - root, - pool, - vote_account_pubkey, - runtime_spad ); - - if( FD_UNLIKELY( !vote_state ) ) { - fd_vote_accounts_pair_global_t_map_release( stakes_vote_accounts_pool, new_vote_node ); - continue; + fd_vote_state_map_t * vote_state_map = fd_vote_states_get_map( vote_states ); + fd_vote_state_ele_t * vote_state_pool = fd_vote_states_get_pool( vote_states ); + for( fd_vote_state_map_iter_t iter = fd_vote_state_map_iter_init( vote_state_map, vote_state_pool ); + !fd_vote_state_map_iter_done( iter, vote_state_map, vote_state_pool ); + iter = fd_vote_state_map_iter_next( iter, vote_state_map, vote_state_pool ) ) { + + fd_vote_state_ele_t const * vote_state = fd_vote_state_map_iter_ele_const( iter, vote_state_map, vote_state_pool ); + + FD_TXN_ACCOUNT_DECL( vote_account ); + if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( vote_account, + &vote_state->vote_account, + slot_ctx->funk, + slot_ctx->funk_txn ) ) ) { + FD_LOG_DEBUG(( "Vote account not found" )); } - // Insert into the epoch stakes cache and temporary vote states cache - fd_vote_accounts_pair_global_t_map_insert( stakes_vote_accounts_pool, &stakes_vote_accounts_root, new_vote_node ); - total_epoch_stake += new_vote_node->elem.stake; + int err; + fd_vote_state_versioned_t * res = fd_bincode_decode_spad( + vote_state_versioned, runtime_spad, + fd_txn_account_get_data( vote_account ), + fd_txn_account_get_data_len( vote_account ), + &err ); + FD_TEST( !!res ); fd_vote_info_pair_t_mapnode_t * new_vote_state_node = fd_vote_info_pair_t_map_acquire( temp_info->vote_states_pool ); - new_vote_state_node->elem.account = *vote_account_pubkey; - new_vote_state_node->elem.state = *vote_state; + new_vote_state_node->elem.account = vote_state->vote_account; + new_vote_state_node->elem.state = *res; fd_vote_info_pair_t_map_insert( temp_info->vote_states_pool, &temp_info->vote_states_root, new_vote_state_node ); + + total_epoch_stake += vote_state->stake; } - fd_vote_accounts_vote_accounts_pool_update( vote_accounts, stakes_vote_accounts_pool ); - fd_vote_accounts_vote_accounts_root_update( vote_accounts, stakes_vote_accounts_root ); - fd_bank_curr_epoch_stakes_end_locking_modify( slot_ctx->bank ); + fd_bank_vote_states_end_locking_modify( slot_ctx->bank ); fd_bank_total_epoch_stake_set( slot_ctx->bank, total_epoch_stake ); - - /* At this point, we need to flush the vote account keys cache */ - vote_account_keys_pool = fd_account_keys_account_keys_pool_join( vote_account_keys ); - vote_account_keys_root = fd_account_keys_account_keys_root_join( vote_account_keys ); - fd_account_keys_pair_t_map_release_tree( vote_account_keys_pool, vote_account_keys_root ); - vote_account_keys_root = NULL; - fd_account_keys_account_keys_pool_update( vote_account_keys, vote_account_keys_pool ); - fd_account_keys_account_keys_root_update( vote_account_keys, vote_account_keys_root ); - fd_bank_vote_account_keys_end_locking_modify( slot_ctx->bank ); } static void diff --git a/src/flamenco/stakes/fd_vote_states.c b/src/flamenco/stakes/fd_vote_states.c new file mode 100644 index 0000000000..aa65732c5c --- /dev/null +++ b/src/flamenco/stakes/fd_vote_states.c @@ -0,0 +1,278 @@ +#include "fd_vote_states.h" + +fd_vote_state_ele_t * +fd_vote_states_get_pool( fd_vote_states_t const * vote_states ) { + FD_SCRATCH_ALLOC_INIT( l, vote_states ); + FD_SCRATCH_ALLOC_APPEND( l, fd_vote_states_align(), sizeof(fd_vote_states_t) ); + uchar * pool = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_states_align(), fd_vote_states_footprint( vote_states->max_vote_accounts ) ); + return fd_vote_state_pool_join( pool ); +} + +fd_vote_state_map_t * +fd_vote_states_get_map( fd_vote_states_t const * vote_states ) { + FD_SCRATCH_ALLOC_INIT( l, vote_states ); + FD_SCRATCH_ALLOC_APPEND( l, fd_vote_states_align(), sizeof(fd_vote_states_t) ); + FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_pool_align(), fd_vote_state_pool_footprint( vote_states->max_vote_accounts ) ); + ulong map_chain_cnt = fd_vote_state_map_chain_cnt_est( vote_states->max_vote_accounts ); + uchar * map = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_map_align(), fd_vote_state_map_footprint( map_chain_cnt ) ); + return fd_vote_state_map_join( map ); +} + +ulong +fd_vote_states_align( void ) { + /* The align of the struct should be the max of the align of the data + structures that it contains. In this case, this is the map, the + pool, and the struct itself */ + return fd_ulong_max( fd_ulong_max( fd_vote_state_map_align(), + fd_vote_state_pool_align() ), alignof(fd_vote_states_t) ); +} + +ulong +fd_vote_states_footprint( ulong max_vote_accounts ) { + + ulong map_chain_cnt = fd_vote_state_map_chain_cnt_est( max_vote_accounts ); + + ulong l = FD_LAYOUT_INIT; + l = FD_LAYOUT_APPEND( l, fd_vote_states_align(), sizeof(fd_vote_states_t) ); + l = FD_LAYOUT_APPEND( l, fd_vote_state_pool_align(), fd_vote_state_pool_footprint( max_vote_accounts ) ); + l = FD_LAYOUT_APPEND( l, fd_vote_state_map_align(), fd_vote_state_map_footprint( map_chain_cnt ) ); + return FD_LAYOUT_FINI( l, fd_vote_states_align() ); +} + +void * +fd_vote_states_new( void * mem, ulong max_vote_accounts ) { + if( FD_UNLIKELY( !mem ) ) { + FD_LOG_WARNING(( "NULL mem" )); + return NULL; + } + + if( FD_UNLIKELY( !max_vote_accounts ) ) { + FD_LOG_WARNING(( "max_vote_accounts is 0" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_vote_states_align() ) ) ) { + FD_LOG_WARNING(( "misaligned mem" )); + return NULL; + } + + ulong map_chain_cnt = fd_vote_state_map_chain_cnt_est( max_vote_accounts ); + + FD_SCRATCH_ALLOC_INIT( l, mem ); + fd_vote_states_t * vote_states = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_states_align(), sizeof(fd_vote_states_t) ); + void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_pool_align(), fd_vote_state_pool_footprint( max_vote_accounts ) ); + void * map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_map_align(), fd_vote_state_map_footprint( map_chain_cnt ) ); + + if( FD_UNLIKELY( FD_SCRATCH_ALLOC_FINI( l, fd_vote_states_align() )!=(ulong)mem+fd_vote_states_footprint( max_vote_accounts ) ) ) { + FD_LOG_WARNING(( "fd_vote_states_new: bad layout" )); + return NULL; + } + + vote_states->magic = FD_VOTE_STATES_MAGIC; + vote_states->max_vote_accounts = max_vote_accounts; + + if( FD_UNLIKELY( !fd_vote_state_pool_new( pool_mem, max_vote_accounts ) ) ) { + FD_LOG_WARNING(( "Failed to create vote states pool" )); + return NULL; + } + + /* TODO: The seed shouldn't be hardcoded. */ + if( FD_UNLIKELY( !fd_vote_state_map_new( map_mem, map_chain_cnt, 999UL ) ) ) { + FD_LOG_WARNING(( "Failed to create vote states map" )); + return NULL; + } + + return mem; +} + +fd_vote_states_t * +fd_vote_states_join( void * mem ) { + if( FD_UNLIKELY( !mem ) ) { + FD_LOG_WARNING(( "NULL mem" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_vote_states_align() ) ) ) { + FD_LOG_WARNING(( "misaligned mem" )); + return NULL; + } + + fd_vote_states_t * vote_states = (fd_vote_states_t *)mem; + + if( FD_UNLIKELY( vote_states->magic != FD_VOTE_STATES_MAGIC ) ) { + FD_LOG_WARNING(( "Invalid vote states magic" )); + return NULL; + } + + ulong map_chain_cnt = fd_vote_state_map_chain_cnt_est( vote_states->max_vote_accounts ); + + FD_SCRATCH_ALLOC_INIT( l, vote_states ); + vote_states = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_states_align(), sizeof(fd_vote_states_t) ); + void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_pool_align(), fd_vote_state_pool_footprint( vote_states->max_vote_accounts ) ); + void * map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_map_align(), fd_vote_state_map_footprint( map_chain_cnt ) ); + + if( FD_UNLIKELY( FD_SCRATCH_ALLOC_FINI( l, fd_vote_states_align() )!=(ulong)mem+fd_vote_states_footprint( vote_states->max_vote_accounts ) ) ) { + FD_LOG_WARNING(( "fd_vote_states_join: bad layout" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_vote_state_pool_join( pool_mem ) ) ) { + FD_LOG_WARNING(( "Failed to join vote states pool" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_vote_state_map_join( map_mem ) ) ) { + FD_LOG_WARNING(( "Failed to join vote states map" )); + return NULL; + } + + return vote_states; +} + +void * +fd_vote_states_leave( fd_vote_states_t * self ) { + if( FD_UNLIKELY( !self ) ) { + FD_LOG_WARNING(( "NULL self" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)self, fd_vote_states_align() ) ) ) { + FD_LOG_WARNING(( "misaligned self" )); + return NULL; + } + + fd_vote_states_t * vote_states = (fd_vote_states_t *)self; + + if( FD_UNLIKELY( vote_states->magic!=FD_VOTE_STATES_MAGIC ) ) { + FD_LOG_WARNING(( "Invalid vote states magic" )); + return NULL; + } + + return (void *)self; +} + +void * +fd_vote_states_delete( void * mem ) { + if( FD_UNLIKELY( !mem ) ) { + FD_LOG_WARNING(( "NULL mem" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_vote_states_align() ) ) ) { + FD_LOG_WARNING(( "misaligned mem" )); + return NULL; + } + + fd_vote_states_t * vote_states = (fd_vote_states_t *)mem; + + if( FD_UNLIKELY( vote_states->magic!=FD_VOTE_STATES_MAGIC ) ) { + FD_LOG_WARNING(( "Invalid vote states magic" )); + return NULL; + } + + vote_states->magic = 0UL; + + return mem; +} + +void +fd_vote_states_update( fd_vote_states_t * self, + fd_pubkey_t * vote_account, + uchar commission, + ulong stake, + ulong credits_cnt, + ushort * epoch, + ulong * credits, + ulong * prev_credits ) { + fd_vote_state_ele_t * vote_state_pool = fd_vote_states_get_pool( self ); + if( FD_UNLIKELY( !vote_state_pool ) ) { + FD_LOG_CRIT(( "unable to retrieve join to vote state pool" )); + } + fd_vote_state_map_t * vote_state_map = fd_vote_states_get_map( self ); + if( FD_UNLIKELY( !vote_state_map ) ) { + FD_LOG_CRIT(( "unable to retrieve join to vote state map" )); + } + + /* First, handle the case where the vote state already exists + and we just need to update the entry. The reason we do a const idx + query is to allow fd_vote_states_update to be called while + iterating over the map. It is unsafe to call + fd_vote_state_map_ele_query() during iteration, but we only + need to change fields which are not used for pool/map management. */ + + ulong idx = fd_vote_state_map_idx_query_const( + vote_state_map, + vote_account, + ULONG_MAX, + vote_state_pool ); + + if( idx!=ULONG_MAX ) { + + fd_vote_state_ele_t * vote_state = fd_vote_state_pool_ele( vote_state_pool, idx ); + if( FD_UNLIKELY( !vote_state ) ) { + FD_LOG_CRIT(( "unable to retrieve vote state" )); + } + + /* TODO: can do something smarter where we only update the + comission and the credits coresponding to the new epoch. */ + vote_state->commission = commission; + vote_state->stake = stake; + vote_state->credits_cnt = credits_cnt; + for( ulong i=0UL; iepoch[i] = epoch[i]; + vote_state->credits[i] = credits[i]; + vote_state->prev_credits[i] = prev_credits[i]; + } + return; + } + + /* If the vote state does not exist, we need to create a new entry. */ + /* Otherwise, try to acquire a new node and populate it. */ + if( FD_UNLIKELY( !fd_vote_state_pool_free( vote_state_pool ) ) ) { + FD_LOG_CRIT(( "no free vote states in pool" )); + } + + fd_vote_state_ele_t * vote_state = fd_vote_state_pool_ele_acquire( vote_state_pool ); + if( FD_UNLIKELY( !vote_state ) ) { + FD_LOG_CRIT(( "unable to acquire vote state" )); + } + + vote_state->commission = commission; + vote_state->stake = stake; + vote_state->credits_cnt = credits_cnt; + for( ulong i=0UL; iepoch[i] = epoch[i]; + vote_state->credits[i] = credits[i]; + vote_state->prev_credits[i] = prev_credits[i]; + } +} + +void +fd_vote_states_remove( fd_vote_states_t * vote_states, + fd_pubkey_t const * vote_account ) { + fd_vote_state_ele_t * vote_state_pool = fd_vote_states_get_pool( vote_states ); + if( FD_UNLIKELY( !vote_state_pool ) ) { + FD_LOG_CRIT(( "unable to retrieve join to stake delegation pool" )); + } + fd_vote_state_map_t * vote_state_map = fd_vote_states_get_map( vote_states ); + if( FD_UNLIKELY( !vote_state_map ) ) { + FD_LOG_CRIT(( "unable to retrieve join to stake delegation map" )); + } + + ulong vote_state_idx = fd_vote_state_map_idx_query( + vote_state_map, + vote_account, + ULONG_MAX, + vote_state_pool ); + if( FD_UNLIKELY( vote_state_idx == ULONG_MAX ) ) { + /* The vote state was not found, nothing to do. */ + return; + } + + ulong idx = fd_vote_state_map_idx_remove( vote_state_map, vote_account, ULONG_MAX, vote_state_pool ); + if( FD_UNLIKELY( idx==ULONG_MAX ) ) { + return; + } + + fd_vote_state_pool_idx_release( vote_state_pool, vote_state_idx ); +} diff --git a/src/flamenco/stakes/fd_vote_states.h b/src/flamenco/stakes/fd_vote_states.h new file mode 100644 index 0000000000..3deab0d149 --- /dev/null +++ b/src/flamenco/stakes/fd_vote_states.h @@ -0,0 +1,165 @@ +#ifndef HEADER_fd_src_flamenco_stakes_fd_vote_states_h +#define HEADER_fd_src_flamenco_stakes_fd_vote_states_h + +#include "../fd_flamenco_base.h" +#include "../types/fd_types.h" + +#define FD_VOTE_STATES_FOOTPRINT (300000000UL) + +#define FD_VOTE_STATES_ALIGN (128UL) + +#define FD_VOTE_STATES_MAGIC (0x01231965UL) + +/* TODO:FIXME: document this */ +#define EPOCH_CREDITS_MAX (64UL) + +/* TODO:FIXME: add handholding here */ + +struct fd_vote_state_ele { + fd_pubkey_t vote_account; + ulong next_; /* Internal pool/map use */ + + ulong credits_cnt; + ushort epoch [ EPOCH_CREDITS_MAX ]; + ulong credits [ EPOCH_CREDITS_MAX ]; + ulong prev_credits[ EPOCH_CREDITS_MAX ]; + + uchar commission; + + ulong stake; +}; +typedef struct fd_vote_state_ele fd_vote_state_ele_t; + +#define POOL_NAME fd_vote_state_pool +#define POOL_T fd_vote_state_ele_t +#define POOL_NEXT next_ +#include "../../util/tmpl/fd_pool.c" + +#define MAP_NAME fd_vote_state_map +#define MAP_KEY_T fd_pubkey_t +#define MAP_ELE_T fd_vote_state_ele_t +#define MAP_KEY vote_account +#define MAP_KEY_EQ(k0,k1) (!(memcmp( &(k0)->key,&(k1)->key,sizeof(fd_pubkey_t) ))) +#define MAP_KEY_HASH(key,seed) (fd_hash( seed, key, sizeof(fd_pubkey_t) )) +#define MAP_NEXT next_ +#include "../../util/tmpl/fd_map_chain.c" + +struct fd_vote_states { + ulong magic; + ulong max_vote_accounts; +}; +typedef struct fd_vote_states fd_vote_states_t; + +FD_PROTOTYPES_BEGIN + +/* fd_vote_states_get_pool returns the underlying pool that the + vote states uses to manage the vote states. */ + +fd_vote_state_ele_t * +fd_vote_states_get_pool( fd_vote_states_t const * vote_states ); + +/* fd_vote_states_get_map returns the underlying map that the + vote states uses to manage the vote states. */ + +fd_vote_state_map_t * +fd_vote_states_get_map( fd_vote_states_t const * vote_states ); + +/* fd_vote_states_align returns the minimum alignment required for a + vote states struct. */ + +ulong +fd_vote_states_align( void ); + +/* fd_vote_states_footprint returns the footprint of the vote states + struct for a given amount of max vote accounts. */ + +ulong +fd_vote_states_footprint( ulong max_vote_accounts ); + +/* fd_vote_states_new creates a new vote states struct + with a given amount of max vote accounts. It formats a memory region + which is sized based off of the number of vote accounts. */ + +void * +fd_vote_states_new( void * mem, ulong max_vote_accounts ); + +/* fd_vote_states_join joins a vote states struct from a + memory region. There can be multiple valid joins for a given memory + region but the caller is responsible for accessing memory in a + thread-safe manner. */ + +fd_vote_states_t * +fd_vote_states_join( void * mem ); + +/* fd_vote_states_leave returns the vote states struct from a memory + region. This function returns a pointer to the vote states struct + and does not take ownership of the memory region. */ + +void * +fd_vote_states_leave( fd_vote_states_t * self ); + +/* fd_vote_states_delete unformats a memory region that was + formatted by fd_vote_states_new. */ + +void * +fd_vote_states_delete( void * mem ); + +/* fd_vote_states_update inserts or updates the vote state corresponding + to a given account. The caller is expected to pass in valid arrays of + epoch, credits, and prev_credits that corresponds to a length of + credits_cnt. */ + +void +fd_vote_states_update( fd_vote_states_t * self, + fd_pubkey_t * vote_account, + uchar commission, + ulong stake, + ulong credits_cnt, + ushort * epoch, + ulong * credits, + ulong * prev_credits ); + +/* fd_vote_states_remove removes the vote state corresponding to a given + account. Does nothing if the account does not exist. */ + +void +fd_vote_states_remove( fd_vote_states_t * vote_states, + fd_pubkey_t const * vote_account ); + +/* fd_vote_states_query returns the vote state corresponding to a given + account. Returns NULL if the account does not exist. */ + +static inline fd_vote_state_ele_t * +fd_vote_states_query( fd_vote_states_t * self, fd_pubkey_t * vote_account ) { + + fd_vote_state_ele_t * vote_state = fd_vote_state_map_ele_query( + fd_vote_states_get_map( self ), + vote_account, + + NULL, fd_vote_states_get_pool( self ) ); + if( FD_UNLIKELY( !vote_state ) ) { + return NULL; + } + + return vote_state; +} + +/* fd_vote_states_max returns the maximum number of vote accounts that + the vote states struct can support. */ + +static inline ulong +fd_vote_states_max( fd_vote_states_t const * vote_states ) { + return vote_states->max_vote_accounts; +} + +/* fd_vote_states_count returns the number of vote states in the vote + states struct. */ + +static inline ulong +fd_vote_states_count( fd_vote_states_t const * vote_states ) { + return fd_vote_state_pool_used( fd_vote_states_get_pool( vote_states ) ); +} + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_stakes_fd_vote_states_h */ diff --git a/src/flamenco/stakes/test_fd_vote_states.c b/src/flamenco/stakes/test_fd_vote_states.c new file mode 100644 index 0000000000..040cc62b6e --- /dev/null +++ b/src/flamenco/stakes/test_fd_vote_states.c @@ -0,0 +1,29 @@ +#include "fd_stake_delegations.h" +#include "../runtime/fd_runtime_const.h" + +int main( int argc, char ** argv ) { + fd_boot( &argc, &argv ); + + char const * name = fd_env_strip_cmdline_cstr ( &argc, &argv, "--wksp", NULL, NULL ); + char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); + ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1UL ); + ulong near_cpu = fd_env_strip_cmdline_ulong( &argc, &argv, "--near-cpu", NULL, fd_log_cpu_id() ); + ulong wksp_tag = fd_env_strip_cmdline_ulong( &argc, &argv, "--wksp-tag", NULL, 1234UL ); + + fd_wksp_t * wksp; + if( name ) { + FD_LOG_NOTICE(( "Attaching to --wksp %s", name )); + wksp = fd_wksp_attach( name ); + } else { + FD_LOG_NOTICE(( "--wksp not specified, using an anonymous local workspace, --page-sz %s, --page-cnt %lu, --near-cpu %lu", + _page_sz, page_cnt, near_cpu )); + wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), page_cnt, near_cpu, "wksp", 0UL ); + } + + FD_TEST( wksp ); + (void)wksp_tag; + + FD_LOG_NOTICE(( "pass" )); + fd_halt(); + return 0; +}