From 7a34721bb44f482ce716aeb8b7a71c765860b060 Mon Sep 17 00:00:00 2001 From: Firedancer Team Date: Sun, 21 Apr 2024 04:02:23 +0000 Subject: [PATCH] flamenco: add stakes module --- .../runtime/program/fd_stake_program.c | 18 + .../runtime/program/fd_stake_program.h | 11 + src/flamenco/runtime/sysvar/Local.mk | 6 + src/flamenco/runtime/sysvar/fd_sysvar.c | 42 ++ src/flamenco/runtime/sysvar/fd_sysvar.h | 15 + .../runtime/sysvar/fd_sysvar_stake_history.c | 78 +++ .../runtime/sysvar/fd_sysvar_stake_history.h | 22 + src/flamenco/stakes/Local.mk | 6 + src/flamenco/stakes/fd_stakes.c | 475 ++++++++++++++++++ src/flamenco/stakes/fd_stakes.h | 58 +++ src/flamenco/stakes/fd_stakes_from_snapshot.c | 302 +++++++++++ 11 files changed, 1033 insertions(+) create mode 100644 src/flamenco/runtime/sysvar/fd_sysvar.c create mode 100644 src/flamenco/runtime/sysvar/fd_sysvar.h create mode 100644 src/flamenco/runtime/sysvar/fd_sysvar_stake_history.c create mode 100644 src/flamenco/runtime/sysvar/fd_sysvar_stake_history.h create mode 100644 src/flamenco/stakes/Local.mk create mode 100644 src/flamenco/stakes/fd_stakes.c create mode 100644 src/flamenco/stakes/fd_stakes.h create mode 100644 src/flamenco/stakes/fd_stakes_from_snapshot.c diff --git a/src/flamenco/runtime/program/fd_stake_program.c b/src/flamenco/runtime/program/fd_stake_program.c index d0f8b6c9ad..d542464f34 100644 --- a/src/flamenco/runtime/program/fd_stake_program.c +++ b/src/flamenco/runtime/program/fd_stake_program.c @@ -2888,3 +2888,21 @@ fd_stake_program_execute( fd_exec_instr_ctx_t ctx ) { done: return rc; } + +/* Public API *********************************************************/ + +int +fd_stake_get_state( fd_borrowed_account_t const * self, + fd_valloc_t const * valloc, + fd_stake_state_v2_t * out ) { + return get_state( self, *valloc, out ); +} + +fd_stake_history_entry_t +fd_stake_activating_and_deactivating( fd_delegation_t const * self, + ulong target_epoch, + fd_stake_history_t const * stake_history, + ulong * new_rate_activation_epoch ) { + return stake_activating_and_deactivating( + self, target_epoch, stake_history, new_rate_activation_epoch ); +} diff --git a/src/flamenco/runtime/program/fd_stake_program.h b/src/flamenco/runtime/program/fd_stake_program.h index 3d8ccac233..4790221eda 100644 --- a/src/flamenco/runtime/program/fd_stake_program.h +++ b/src/flamenco/runtime/program/fd_stake_program.h @@ -22,6 +22,17 @@ FD_PROTOTYPES_BEGIN int fd_stake_program_execute( fd_exec_instr_ctx_t ctx ); +int +fd_stake_get_state( fd_borrowed_account_t const * self, + fd_valloc_t const * valloc, + fd_stake_state_v2_t * out ); + +fd_stake_history_entry_t +fd_stake_activating_and_deactivating( fd_delegation_t const * self, + ulong target_epoch, + fd_stake_history_t const * stake_history, + ulong * new_rate_activation_epoch ); + FD_PROTOTYPES_END #endif /* HEADER_fd_src_flamenco_runtime_program_fd_stake_program_h */ diff --git a/src/flamenco/runtime/sysvar/Local.mk b/src/flamenco/runtime/sysvar/Local.mk index 5ef213e086..a981341864 100644 --- a/src/flamenco/runtime/sysvar/Local.mk +++ b/src/flamenco/runtime/sysvar/Local.mk @@ -1,4 +1,7 @@ ifdef FD_HAS_INT128 +$(call add-hdrs,fd_sysvar.h) +$(call add-objs,fd_sysvar,fd_flamenco) + $(call add-hdrs,fd_sysvar_cache.h) $(call add-objs,fd_sysvar_cache,fd_flamenco) @@ -20,4 +23,7 @@ $(call run-unit-test,test_sysvar_rent) $(call add-hdrs,fd_sysvar_slot_hashes.h) $(call add-objs,fd_sysvar_slot_hashes,fd_flamenco) + +$(call add-hdrs,fd_sysvar_stake_history.h) +$(call add-objs,fd_sysvar_stake_history,fd_flamenco) endif diff --git a/src/flamenco/runtime/sysvar/fd_sysvar.c b/src/flamenco/runtime/sysvar/fd_sysvar.c new file mode 100644 index 0000000000..7388de094f --- /dev/null +++ b/src/flamenco/runtime/sysvar/fd_sysvar.c @@ -0,0 +1,42 @@ +#include "fd_sysvar.h" +#include "../context/fd_exec_epoch_ctx.h" +#include "../context/fd_exec_slot_ctx.h" +#include "fd_sysvar_rent.h" + +int +fd_sysvar_set( fd_exec_slot_ctx_t * slot_ctx, + uchar const * owner, + fd_pubkey_t const * pubkey, + uchar * data, + ulong sz, + ulong slot, + ulong lamports ) { + + fd_acc_mgr_t * acc_mgr = slot_ctx->acc_mgr; + fd_funk_txn_t * funk_txn = slot_ctx->funk_txn; + + FD_BORROWED_ACCOUNT_DECL(rec); + + int err = fd_acc_mgr_modify( acc_mgr, funk_txn, pubkey, 1, sz, rec ); + if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) + return FD_ACC_MGR_ERR_READ_FAILED; + + fd_memcpy(rec->data, data, sz); + // What is the correct behavior here? Where is this code in the + // solana code base? Do I only adjust the lamports if the data + // increases but not decreases? I am inventing money here... + fd_acc_lamports_t lamports_before = rec->meta->info.lamports; + rec->meta->info.lamports = (lamports == 0UL) ? fd_rent_exempt_minimum_balance2( &slot_ctx->epoch_ctx->epoch_bank.rent, sz ) : lamports; + slot_ctx->slot_bank.capitalization = fd_ulong_sat_sub( + fd_ulong_sat_add( + slot_ctx->slot_bank.capitalization, + rec->meta->info.lamports), + lamports_before); + // FD_LOG_DEBUG(("fd_sysvar_set: capitalization={%lu} increased by lamports: %lu for pubkey %32J", slot_ctx->slot_bank.capitalization, (rec->meta->info.lamports - lamports_before), pubkey)); + + + rec->meta->dlen = sz; + fd_memcpy(rec->meta->info.owner, owner, 32); + rec->meta->slot = slot; + return 0; +} diff --git a/src/flamenco/runtime/sysvar/fd_sysvar.h b/src/flamenco/runtime/sysvar/fd_sysvar.h new file mode 100644 index 0000000000..710eebb248 --- /dev/null +++ b/src/flamenco/runtime/sysvar/fd_sysvar.h @@ -0,0 +1,15 @@ +#ifndef HEADER_fd_src_flamenco_runtime_fd_sysvar_h +#define HEADER_fd_src_flamenco_runtime_fd_sysvar_h + +#include "../../fd_flamenco_base.h" + +int +fd_sysvar_set( fd_exec_slot_ctx_t * state, + uchar const * owner, + fd_pubkey_t const * pubkey, + uchar * data, + ulong sz, + ulong slot, + ulong lamports ); + +#endif /* HEADER_fd_src_flamenco_runtime_fd_sysvar_h */ diff --git a/src/flamenco/runtime/sysvar/fd_sysvar_stake_history.c b/src/flamenco/runtime/sysvar/fd_sysvar_stake_history.c new file mode 100644 index 0000000000..f1e03be1a6 --- /dev/null +++ b/src/flamenco/runtime/sysvar/fd_sysvar_stake_history.c @@ -0,0 +1,78 @@ +#include "fd_sysvar_stake_history.h" +#include "../context/fd_exec_slot_ctx.h" +#include "../../types/fd_types.h" +#include "fd_sysvar.h" +#include "../fd_system_ids.h" + +void +write_stake_history( fd_exec_slot_ctx_t * slot_ctx, + fd_stake_history_t * stake_history ) { + /* https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/sdk/program/src/sysvar/stake_history.rs#L12 */ + uchar enc[16392] = {0}; + + fd_bincode_encode_ctx_t encode = + { .data = enc, + .dataend = enc + sizeof(enc) }; + if( FD_UNLIKELY( fd_stake_history_encode( stake_history, &encode )!=FD_BINCODE_SUCCESS ) ) + FD_LOG_ERR(("fd_stake_history_encode failed")); + + fd_sysvar_set( slot_ctx, fd_sysvar_owner_id.key, &fd_sysvar_stake_history_id, enc, sizeof(enc), slot_ctx->slot_bank.slot, 0UL ); +} + +fd_stake_history_t * +fd_sysvar_stake_history_read( fd_stake_history_t * result, + fd_exec_slot_ctx_t * slot_ctx, + fd_valloc_t * valloc ) { + + FD_BORROWED_ACCOUNT_DECL(stake_rec); + int err = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, &fd_sysvar_stake_history_id, stake_rec); + if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) + return NULL; + + fd_bincode_decode_ctx_t ctx = { + .data = stake_rec->const_data, + .dataend = (char *) stake_rec->const_data + stake_rec->const_meta->dlen, + .valloc = *valloc + }; + + if( FD_UNLIKELY( fd_stake_history_decode( result, &ctx )!=FD_BINCODE_SUCCESS ) ) + return NULL; + return result; +} + +void +fd_sysvar_stake_history_init( fd_exec_slot_ctx_t * slot_ctx ) { + fd_stake_history_t stake_history = { + .pool = fd_stake_history_pool_alloc( slot_ctx->valloc ), + .treap = fd_stake_history_treap_alloc( slot_ctx->valloc ) + }; + write_stake_history( slot_ctx, &stake_history ); +} + +void +fd_sysvar_stake_history_update( fd_exec_slot_ctx_t * slot_ctx, + fd_stake_history_entry_t * entry ) { + // Need to make this maybe zero copies of map... + fd_stake_history_t stake_history; + fd_sysvar_stake_history_read( &stake_history, slot_ctx, &slot_ctx->valloc ); + + if (fd_stake_history_treap_ele_cnt( stake_history.treap ) == fd_stake_history_treap_ele_max( stake_history.treap )) { + fd_stake_history_treap_fwd_iter_t iter = fd_stake_history_treap_fwd_iter_init( stake_history.treap, stake_history.pool ); + fd_stake_history_entry_t * ele = fd_stake_history_treap_fwd_iter_ele( iter, stake_history.pool ); + stake_history.treap = fd_stake_history_treap_ele_remove( stake_history.treap, ele, stake_history.pool ); + fd_stake_history_pool_ele_release( stake_history.pool, ele ); + } + + ulong idx = fd_stake_history_pool_idx_acquire( stake_history.pool ); + + stake_history.pool[ idx ].epoch = entry->epoch; + stake_history.pool[ idx ].activating = entry->activating; + stake_history.pool[ idx ].effective = entry->effective; + stake_history.pool[ idx ].deactivating = entry->deactivating; + stake_history.treap = fd_stake_history_treap_idx_insert( stake_history.treap, idx, stake_history.pool ); + + + write_stake_history( slot_ctx, &stake_history); + fd_bincode_destroy_ctx_t destroy = { .valloc = slot_ctx->valloc }; + fd_stake_history_destroy( &stake_history, &destroy ); +} diff --git a/src/flamenco/runtime/sysvar/fd_sysvar_stake_history.h b/src/flamenco/runtime/sysvar/fd_sysvar_stake_history.h new file mode 100644 index 0000000000..a5885efef9 --- /dev/null +++ b/src/flamenco/runtime/sysvar/fd_sysvar_stake_history.h @@ -0,0 +1,22 @@ +#ifndef HEADER_fd_src_flamenco_runtime_fd_sysvar_stake_history_h +#define HEADER_fd_src_flamenco_runtime_fd_sysvar_stake_history_h + +#include "../../fd_flamenco_base.h" +#include "../fd_executor.h" + +FD_PROTOTYPES_BEGIN + +/* The stake history sysvar contains the history of cluster-wide activations and de-activations per-epoch. Updated at the start of each epoch. */ + +/* Initialize the stake history sysvar account. */ +void +fd_sysvar_stake_history_init( fd_exec_slot_ctx_t * slot_ctx ); + +/* Update the stake history sysvar account - called during epoch boundary*/ +void +fd_sysvar_stake_history_update( fd_exec_slot_ctx_t * slot_ctx, + fd_stake_history_entry_t * entry ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_runtime_fd_sysvar_stake_history_h */ diff --git a/src/flamenco/stakes/Local.mk b/src/flamenco/stakes/Local.mk new file mode 100644 index 0000000000..e032fad6c8 --- /dev/null +++ b/src/flamenco/stakes/Local.mk @@ -0,0 +1,6 @@ +ifdef FD_HAS_INT128 +$(call add-hdrs,fd_stakes.h) +$(call add-objs,fd_stakes,fd_flamenco) +# TODO this should not depend on fd_funk +$(call make-bin,fd_stakes_from_snapshot,fd_stakes_from_snapshot,fd_flamenco fd_funk fd_ballet fd_util) +endif diff --git a/src/flamenco/stakes/fd_stakes.c b/src/flamenco/stakes/fd_stakes.c new file mode 100644 index 0000000000..789c3dcf15 --- /dev/null +++ b/src/flamenco/stakes/fd_stakes.c @@ -0,0 +1,475 @@ +#include "fd_stakes.h" +#include "../runtime/fd_system_ids.h" +#include "../runtime/context/fd_exec_epoch_ctx.h" +#include "../runtime/context/fd_exec_slot_ctx.h" +#include "../runtime/program/fd_stake_program.h" +#include "../runtime/sysvar/fd_sysvar_stake_history.h" + +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" + +/* fd_stakes_accum_by_node converts Stakes (unordered list of (vote acc, + active stake) tuples) to StakedNodes (rbtree mapping (node identity) + => (active stake) ordered by node identity). Returns the tree root. */ + +static fd_stake_weight_t_mapnode_t * +fd_stakes_accum_by_node( fd_vote_accounts_t const * in, + fd_stake_weight_t_mapnode_t * out_pool ) { + + /* Stakes::staked_nodes(&self: Stakes) -> HashMap */ + + fd_vote_accounts_pair_t_mapnode_t * in_pool = in->vote_accounts_pool; + fd_vote_accounts_pair_t_mapnode_t * in_root = in->vote_accounts_root; + + /* VoteAccounts::staked_nodes(&self: VoteAccounts) -> HashMap */ + + /* For each active vote account, accumulate (node_identity, stake) by + summing stake. */ + + fd_stake_weight_t_mapnode_t * out_root = NULL; + + for( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum( in_pool, in_root ); + n; + n = fd_vote_accounts_pair_t_map_successor( in_pool, n ) ) { + + /* ... filter(|(stake, _)| *stake != 0u64) */ + if( n->elem.stake == 0UL ) continue; + + /* Create scratch allocator for current scope */ + FD_SCRATCH_SCOPE_BEGIN { + + fd_valloc_t scratch = fd_scratch_virtual(); + + /* Decode vote account */ + uchar const * vote_acc_data = n->elem.value.data; + fd_bincode_decode_ctx_t decode_ctx = { + .data = vote_acc_data, + .dataend = vote_acc_data + n->elem.value.data_len, + .valloc = scratch, + }; + fd_vote_state_versioned_t vote_state_versioned; + if( FD_UNLIKELY( 0!=fd_vote_state_versioned_decode( &vote_state_versioned, &decode_ctx ) ) ) { + /* TODO can this occur on a real cluster? */ + FD_LOG_WARNING(( "Failed to deserialize vote account %32J", n->elem.key.key )); + continue; + } + + /* Extract node pubkey */ + fd_pubkey_t const * node_pubkey; + switch( vote_state_versioned.discriminant ) { + case fd_vote_state_versioned_enum_v0_23_5: + node_pubkey = &vote_state_versioned.inner.v0_23_5.node_pubkey; break; + case fd_vote_state_versioned_enum_v1_14_11: + node_pubkey = &vote_state_versioned.inner.v1_14_11.node_pubkey; break; + case fd_vote_state_versioned_enum_current: + node_pubkey = &vote_state_versioned.inner.current.node_pubkey; break; + default: + FD_LOG_WARNING(( "Unrecognized vote version in account %32J", n->elem.key.key )); + continue; + } + + /* Check if node identity was previously visited */ + fd_stake_weight_t_mapnode_t * query = fd_stake_weight_t_map_acquire( out_pool ); + FD_TEST( query ); + query->elem.key = *node_pubkey; + fd_stake_weight_t_mapnode_t * node = fd_stake_weight_t_map_find( out_pool, out_root, query ); + + if( FD_UNLIKELY( node ) ) { + /* Accumulate to previously created entry */ + fd_stake_weight_t_map_release( out_pool, query ); + node->elem.stake += n->elem.stake; + } else { + /* Create new entry */ + node = query; + node->elem.stake = n->elem.stake; + fd_stake_weight_t_map_insert( out_pool, &out_root, node ); + } + } FD_SCRATCH_SCOPE_END; + } + + return out_root; +} + +/* fd_stake_weight_sort sorts the given array of stake weights with + length stakes_cnt by tuple (stake, pubkey) in descending order. */ + +FD_FN_CONST static int +fd_stakes_sort_before( fd_stake_weight_t a, + fd_stake_weight_t b ) { + + if( a.stake > b.stake ) return 1; + if( a.stake < b.stake ) return 0; + if( memcmp( &a.key, &b.key, 32UL )>0 ) return 1; + return 0; +} + +#define SORT_NAME fd_stakes_sort +#define SORT_KEY_T fd_stake_weight_t +#define SORT_BEFORE(a,b) fd_stakes_sort_before( (a), (b) ) +#include "../../util/tmpl/fd_sort.c" + +void +fd_stake_weight_sort( fd_stake_weight_t * stakes, + ulong stakes_cnt ) { + fd_stakes_sort_inplace( stakes, stakes_cnt ); +} + +/* fd_stakes_export_sorted converts StakedNodes (rbtree mapping + (node identity) => (active stake) from fd_stakes_accum_by_node) to + a list of fd_stake_weights_t. */ + +static ulong +fd_stakes_export( fd_stake_weight_t_mapnode_t const * const in_pool, + fd_stake_weight_t_mapnode_t const * const root, + fd_stake_weight_t * const out ) { + + fd_stake_weight_t * out_end = out; + + for( fd_stake_weight_t_mapnode_t const * ele = fd_stake_weight_t_map_minimum( (fd_stake_weight_t_mapnode_t *)in_pool, (fd_stake_weight_t_mapnode_t *)root ); ele; ele = (fd_stake_weight_t_mapnode_t *)fd_stake_weight_t_map_successor( (fd_stake_weight_t_mapnode_t *)in_pool, (fd_stake_weight_t_mapnode_t *)ele ) ) { + *out_end++ = ele->elem; + } + + return (ulong)( out_end - out ); +} + +ulong +fd_stake_weights_by_node( fd_vote_accounts_t const * accs, + fd_stake_weight_t * weights ) { + + /* Enter scratch frame for duration for function */ + + if( FD_UNLIKELY( !fd_scratch_push_is_safe() ) ) { + FD_LOG_WARNING(( "fd_scratch_push() failed" )); + return ULONG_MAX; + } + + FD_SCRATCH_SCOPE_BEGIN { + + /* Estimate size required to store temporary data structures */ + + /* TODO size is the wrong method name for this */ + ulong vote_acc_cnt = fd_vote_accounts_pair_t_map_size( accs->vote_accounts_pool, accs->vote_accounts_root ); + + ulong rb_align = fd_stake_weight_t_map_align(); + ulong rb_footprint = fd_stake_weight_t_map_footprint( vote_acc_cnt ); + + if( FD_UNLIKELY( !fd_scratch_alloc_is_safe( rb_align, rb_footprint ) ) ) { + FD_LOG_WARNING(( "insufficient scratch space: need %lu align %lu footprint", + rb_align, rb_footprint )); + return ULONG_MAX; + } + + /* Create rb tree */ + + void * pool_mem = fd_scratch_alloc( rb_align, rb_footprint ); + pool_mem = fd_stake_weight_t_map_new( pool_mem, vote_acc_cnt ); + fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join( pool_mem ); + if( FD_UNLIKELY( !pool_mem ) ) FD_LOG_CRIT(( "fd_stake_weights_new() failed" )); + + /* Accumulate stakes to rb tree */ + + fd_stake_weight_t_mapnode_t const * root = fd_stakes_accum_by_node( accs, pool ); + + /* Export to sorted list */ + + ulong weights_cnt = fd_stakes_export( pool, root, weights ); + fd_stake_weight_sort( weights, weights_cnt ); + + return weights_cnt; + } FD_SCRATCH_SCOPE_END; +} + +/* +Refresh vote accounts. + +This updates the epoch bank stakes vote_accounts cache - that is, the total amount +of delegated stake each vote account has, using the current delegation values from inside each +stake account. + +https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L562 */ +void +refresh_vote_accounts( fd_exec_slot_ctx_t * slot_ctx, + fd_stake_history_t const * history ) { + fd_stakes_t * stakes = &slot_ctx->epoch_ctx->epoch_bank.stakes; + + FD_SCRATCH_SCOPE_BEGIN { + + // Create a map of to store the total stake of each vote account. + static const ulong maplen = 10000; + void * mem = fd_scratch_alloc( fd_stake_weight_t_map_align(), fd_stake_weight_t_map_footprint(maplen)); + fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_join(fd_stake_weight_t_map_new(mem, maplen)); + fd_stake_weight_t_mapnode_t * root = NULL; + ulong * new_rate_activation_epoch = NULL; + + // Iterate over each stake delegation and accumulate the stake amount associated with the given vote account. + for ( + fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum(stakes->stake_delegations_pool, stakes->stake_delegations_root); + n; + n = fd_delegation_pair_t_map_successor(stakes->stake_delegations_pool, n) ) { + + // Get the stake account + FD_BORROWED_ACCOUNT_DECL(stake_acc); + int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.account, stake_acc); + if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || stake_acc->const_meta->info.lamports == 0 ) ) { + continue; + } + + fd_stake_state_v2_t stake_state; + rc = fd_stake_get_state( stake_acc, &slot_ctx->valloc, &stake_state ); + if ( FD_UNLIKELY( rc != 0) ) { + continue; + } + + // Fetch the delegation associated with this stake account + fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation; + fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( + delegation, stakes->epoch, history, new_rate_activation_epoch ); + + // Add this delegation amount to the total stake of the vote account + ulong delegation_stake = new_entry.effective; + fd_stake_weight_t_mapnode_t temp; + fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t)); + fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp); + if (entry != NULL) { + entry->elem.stake += delegation_stake; + } else { + entry = fd_stake_weight_t_map_acquire( pool ); + fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t)); + entry->elem.stake = delegation_stake; + fd_stake_weight_t_map_insert( pool, &root, entry ); + } + } + + // Also include delegations from the stake accounts in the current slot context's + // slot_ctx->slot_bank.stake_account_keys (a set of the stake accounts which we have + // from this epoch). + for ( fd_stake_accounts_pair_t_mapnode_t * n = fd_stake_accounts_pair_t_map_minimum( + slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, + slot_ctx->slot_bank.stake_account_keys.stake_accounts_root); + n; + n = fd_stake_accounts_pair_t_map_successor( slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, n ) ) { + FD_BORROWED_ACCOUNT_DECL(stake_acc); + int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.key, stake_acc); + if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || stake_acc->const_meta->info.lamports == 0 ) ) { + continue; + } + + fd_stake_state_v2_t stake_state; + rc = fd_stake_get_state( stake_acc, &slot_ctx->valloc, &stake_state ); + if ( FD_UNLIKELY( rc != 0) ) { + continue; + } + + fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation; + fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch ); + + ulong delegation_stake = new_entry.effective; + fd_stake_weight_t_mapnode_t temp; + fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t)); + fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp); + if (entry != NULL) { + entry->elem.stake += delegation_stake; + } else { + entry = fd_stake_weight_t_map_acquire( pool ); + fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t)); + entry->elem.stake = delegation_stake; + fd_stake_weight_t_map_insert( pool, &root, entry ); + } + } + + // Copy the delegated stake values calculated above to the epoch bank stakes vote_accounts + for ( fd_vote_accounts_pair_t_mapnode_t * n = + fd_vote_accounts_pair_t_map_minimum( + stakes->vote_accounts.vote_accounts_pool, stakes->vote_accounts.vote_accounts_root); + n; + n = fd_vote_accounts_pair_t_map_successor(stakes->vote_accounts.vote_accounts_pool, n) ) { + fd_stake_weight_t_mapnode_t temp; + memcpy(&temp.elem.key, &n->elem.key, sizeof(fd_pubkey_t)); + fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp); + n->elem.stake = (entry == NULL) ? 0 : entry->elem.stake; + } + + // Copy the delegated stake values calculated above to the slot bank stakes vote_accounts + for ( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum( slot_ctx->slot_bank.vote_account_keys.vote_accounts_pool, slot_ctx->slot_bank.vote_account_keys.vote_accounts_root ); + n; + n = fd_vote_accounts_pair_t_map_successor( slot_ctx->slot_bank.vote_account_keys.vote_accounts_pool, n )) { + fd_stake_weight_t_mapnode_t temp; + memcpy(&temp.elem.key, &n->elem.key, sizeof(fd_pubkey_t)); + fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp); + n->elem.stake = (entry == NULL) ? 0 : entry->elem.stake; + } + + } FD_SCRATCH_SCOPE_END; +} + +/* https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L169 */ +void +fd_stakes_activate_epoch( fd_exec_slot_ctx_t * slot_ctx, + ulong next_epoch ) { + + fd_stakes_t * stakes = &slot_ctx->epoch_ctx->epoch_bank.stakes; + + /* Current stake delegations: list of all current delegations in stake_delegations + https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L180 */ + /* Add a new entry to the Stake History sysvar for the previous epoch + https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L181-L192 */ + + fd_stake_history_t const * history = fd_sysvar_cache_stake_history( slot_ctx->sysvar_cache ); + if( FD_UNLIKELY( !history ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" )); + + fd_stake_history_entry_t accumulator = { + .effective = 0, + .activating = 0, + .deactivating = 0 + }; + + fd_stake_weight_t_mapnode_t * pool = fd_stake_weight_t_map_alloc(slot_ctx->valloc, 10000); + fd_stake_weight_t_mapnode_t * root = NULL; + + ulong * new_rate_activation_epoch = NULL; + for ( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum(stakes->stake_delegations_pool, stakes->stake_delegations_root); n; n = fd_delegation_pair_t_map_successor(stakes->stake_delegations_pool, n) ) { + FD_BORROWED_ACCOUNT_DECL(acc); + int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.account, acc); + if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || acc->const_meta->info.lamports == 0 ) ) { + continue; + } + + fd_stake_state_v2_t stake_state; + rc = fd_stake_get_state( acc, &slot_ctx->valloc, &stake_state ); + if ( FD_UNLIKELY( rc != 0) ) { + continue; + } + + fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation; + fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch ); + accumulator.effective += new_entry.effective; + accumulator.activating += new_entry.activating; + accumulator.deactivating += new_entry.deactivating; + + ulong delegation_stake = new_entry.effective; + fd_stake_weight_t_mapnode_t temp; + fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t)); + fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp); + if (entry != NULL) { + entry->elem.stake += delegation_stake; + } else { + entry = fd_stake_weight_t_map_acquire( pool ); + fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t)); + entry->elem.stake = delegation_stake; + fd_stake_weight_t_map_insert( pool, &root, entry ); + } + } + + for ( fd_stake_accounts_pair_t_mapnode_t * n = fd_stake_accounts_pair_t_map_minimum( slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, slot_ctx->slot_bank.stake_account_keys.stake_accounts_root); + n; + n = fd_stake_accounts_pair_t_map_successor( slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, n ) ) { + FD_BORROWED_ACCOUNT_DECL(acc); + int rc = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, &n->elem.key, acc); + if ( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS || acc->const_meta->info.lamports == 0 ) ) { + continue; + } + + fd_stake_state_v2_t stake_state; + rc = fd_stake_get_state( acc, &slot_ctx->valloc, &stake_state ); + if ( FD_UNLIKELY( rc != 0) ) { + continue; + } + + fd_delegation_t * delegation = &stake_state.inner.stake.stake.delegation; + fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating( delegation, stakes->epoch, history, new_rate_activation_epoch ); + accumulator.effective += new_entry.effective; + accumulator.activating += new_entry.activating; + accumulator.deactivating += new_entry.deactivating; + + ulong delegation_stake = new_entry.effective; + fd_stake_weight_t_mapnode_t temp; + fd_memcpy(&temp.elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t)); + fd_stake_weight_t_mapnode_t * entry = fd_stake_weight_t_map_find(pool, root, &temp); + if (entry != NULL) { + entry->elem.stake += delegation_stake; + } else { + entry = fd_stake_weight_t_map_acquire( pool ); + fd_memcpy( &entry->elem.key, &delegation->voter_pubkey, sizeof(fd_pubkey_t)); + entry->elem.stake = delegation_stake; + fd_stake_weight_t_map_insert( pool, &root, entry ); + } + } + + fd_stake_history_entry_t new_elem = { + .epoch = stakes->epoch, + .effective = accumulator.effective, + .activating = accumulator.activating, + .deactivating = accumulator.deactivating + }; + + fd_sysvar_stake_history_update( slot_ctx, &new_elem); + + /* Update the current epoch value */ + stakes->epoch = next_epoch; + + fd_valloc_free( slot_ctx->valloc, + fd_stake_weight_t_map_delete( fd_stake_weight_t_map_leave ( pool ) ) ); + + // Update the list of vote accounts in the epoch stake cache + // https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L314 + // refresh_vote_accounts( slot_ctx, &history ); + + // TODO: Update epoch stakes? + // refresh_vote_accounts( slot_ctx, &history ); + + // ulong sz = fd_vote_accounts_pair_t_map_size( slot_ctx->epoch_ctx->epoch_bank.stakes.vote_accounts.vote_accounts_pool, slot_ctx->epoch_ctx->epoch_bank.stakes.vote_accounts.vote_accounts_root ); + // fd_vote_accounts_pair_t_mapnode_t * new_vote_root = NULL; + // fd_vote_accounts_pair_t_mapnode_t * new_vote_pool = fd_vote_accounts_pair_t_map_alloc( slot_ctx->valloc, sz ); + // fd_bincode_destroy_ctx_t destroy = {.valloc = slot_ctx->valloc}; + + // for ( fd_vote_accounts_pair_t_mapnode_t const * n = fd_vote_accounts_pair_t_map_minimum_const( slot_ctx->epoch_ctx->epoch_bank.stakes.vote_accounts.vote_accounts_pool, slot_ctx->epoch_ctx->epoch_bank.stakes.vote_accounts.vote_accounts_root ); + // n; + // n = fd_vote_accounts_pair_t_map_successor_const( slot_ctx->epoch_ctx->epoch_bank.stakes.vote_accounts.vote_accounts_pool, n )) { + // fd_vote_accounts_pair_t_mapnode_t * entry = fd_vote_accounts_pair_t_map_acquire( new_vote_pool ); + // fd_memcpy( &entry->elem, &n->elem, sizeof(fd_vote_accounts_pair_t)); + // fd_vote_accounts_pair_t_map_insert( new_vote_pool, &new_vote_root, entry ); + // } + // fd_vote_accounts_destroy( &slot_ctx->slot_bank.epoch_stakes, &destroy ); + + // slot_ctx->slot_bank.epoch_stakes.vote_accounts_root = new_vote_root; + // slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool = new_vote_pool; + +} + +int +write_stake_state( fd_exec_slot_ctx_t * global, + fd_pubkey_t const * stake_acc, + fd_stake_state_v2_t * stake_state, + ushort is_new_account ) { + // TODO + (void)stake_state; + + ulong encoded_stake_state_size = (is_new_account) ? STAKE_ACCOUNT_SIZE : fd_stake_state_v2_size(stake_state); + + FD_BORROWED_ACCOUNT_DECL(stake_acc_rec); + + int err = fd_acc_mgr_modify( global->acc_mgr, global->funk_txn, stake_acc, !!is_new_account, encoded_stake_state_size, stake_acc_rec ); + if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) { + FD_LOG_WARNING(( "write_stake_state failed" )); + return err; + } + + if (is_new_account) + fd_memset( stake_acc_rec->data, 0, encoded_stake_state_size ); + + fd_bincode_encode_ctx_t ctx3; + ctx3.data = stake_acc_rec->data; + ctx3.dataend = stake_acc_rec->data + encoded_stake_state_size; + if( FD_UNLIKELY( fd_stake_state_v2_encode( stake_state, &ctx3 )!=FD_BINCODE_SUCCESS ) ) + FD_LOG_ERR(("fd_stake_state_encode failed")); + + if( is_new_account ) { + stake_acc_rec->meta->dlen = STAKE_ACCOUNT_SIZE; + /* TODO Lamports? */ + stake_acc_rec->meta->info.executable = 0; + stake_acc_rec->meta->info.rent_epoch = 0UL; + memcpy( &stake_acc_rec->meta->info.owner, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ); + } + + return 0; +} diff --git a/src/flamenco/stakes/fd_stakes.h b/src/flamenco/stakes/fd_stakes.h new file mode 100644 index 0000000000..0f12b18224 --- /dev/null +++ b/src/flamenco/stakes/fd_stakes.h @@ -0,0 +1,58 @@ +#ifndef HEADER_fd_src_flamenco_stakes_fd_stakes_h +#define HEADER_fd_src_flamenco_stakes_fd_stakes_h + +#include "../fd_flamenco_base.h" +#include "../types/fd_types.h" +#include "../runtime/fd_borrowed_account.h" + +FD_PROTOTYPES_BEGIN + +/* fd_stake_weights_by_node converts Stakes (unordered list of (vote + acc, active stake) tuples) to an ordered list of (stake, node + identity) sorted by (stake descending, node identity descending). + + weights points to an array suitable to hold ... + + fd_vote_accounts_pair_t_map_size( accs->vote_accounts_pool, + accs->vote_accounts_root ) + + ... items. On return, weights be an ordered list. + + Returns the number of items in weights (which is <= no of vote accs). + On failure returns ULONG_MAX. Reasons for failure include not enough + scratch space available. */ +#define STAKE_ACCOUNT_SIZE ( 200 ) + +ulong +fd_stake_weights_by_node( fd_vote_accounts_t const * accs, + fd_stake_weight_t * weights ); + + +void +fd_stakes_activate_epoch( fd_exec_slot_ctx_t * global, + ulong next_epoch ); + +fd_stake_history_entry_t stake_and_activating( fd_delegation_t const * delegation, ulong target_epoch, fd_stake_history_t * stake_history, ulong * new_rate_activation_epoch ); + +fd_stake_history_entry_t stake_activating_and_deactivating( fd_delegation_t const * delegation, ulong target_epoch, fd_stake_history_t * stake_history, ulong * new_rate_activation_epoch ); + +int write_stake_state( + fd_exec_slot_ctx_t* global, + fd_pubkey_t const * stake_acc, + fd_stake_state_v2_t* stake_state, + ushort is_new_account +); + +void +fd_stakes_remove_stake_delegation( fd_exec_slot_ctx_t * slot_ctx, fd_borrowed_account_t * stake_account, ulong * new_rate_activation_epoch ); + +void +fd_stakes_upsert_stake_delegation( fd_exec_slot_ctx_t * slot_ctx, fd_borrowed_account_t * stake_account, ulong * new_rate_activation_epoch ); + +void +refresh_vote_accounts( fd_exec_slot_ctx_t * slot_ctx, + fd_stake_history_t const * history ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_stakes_fd_stakes_h */ diff --git a/src/flamenco/stakes/fd_stakes_from_snapshot.c b/src/flamenco/stakes/fd_stakes_from_snapshot.c new file mode 100644 index 0000000000..af6ff83ea8 --- /dev/null +++ b/src/flamenco/stakes/fd_stakes_from_snapshot.c @@ -0,0 +1,302 @@ +#define FD_SCRATCH_USE_HANDHOLDING 1 +#include "../fd_flamenco.h" +#include "../../ballet/base58/fd_base58.h" +#include "../types/fd_types.h" +#include "../leaders/fd_leaders.h" +#include "../runtime/sysvar/fd_sysvar_epoch_schedule.h" +#include "fd_stakes.h" + +#include +#include +#include +#include + +static int +usage( void ) { + fprintf( stderr, + "usage: fd_stakes_from_snapshot {nodes/leaders} {FILE}\n" + "\n" + "Derive epoch stake information from snapshot.\n" + "\n" + "Mode:\n" + " epochs Print available epochs\n" + " nodes Dump active stake per node identity\n" + " CSV format: {pubkey},{stake}\n" + " leaders Dump leader schedule\n" + " CSV format: {slot},{pubkey}\n" + "\n" + "FILE is the file path to a .tar.zst snapshot or a raw\n" + " bincode snapshot manifest\n" + "\n" + "Options:\n" + "\n" + " --page-sz {gigantic|huge|normal} Page size\n" + " --page-cnt 2 Page count\n" + " --scratch-mb 1024 Scratch mem MiB\n" + " --epoch Epoch number\n" ); + return 0; +} + +#define ACTION_NODES (0) +#define ACTION_LEADERS (1) +#define ACTION_EPOCHS (2) + +/* _find_epoch looks for epoch stakes for the requested epoch. + On failure, logs error and aborts application. */ + +static fd_epoch_stakes_t const * +_find_epoch( fd_solana_manifest_t const * manifest, + ulong epoch ) { + + + if( FD_UNLIKELY( epoch==ULONG_MAX ) ) { + fprintf( stderr, "error: missing --epoch\n" ); + usage(); + exit(1); + } + + fd_epoch_stakes_t const * stakes = NULL; + fd_epoch_epoch_stakes_pair_t const * epochs = manifest->bank.epoch_stakes; + for( ulong i=0; i < manifest->bank.epoch_stakes_len; i++ ) { + if( epochs[ i ].key==epoch ) { + stakes = &epochs[i].value; + break; + } + } + if( FD_UNLIKELY( !stakes ) ) + FD_LOG_ERR(( "Snapshot missing EpochStakes for epoch %lu", epoch )); + + return stakes; +} + +static fd_stake_weight_t * +_get_stake_weights( fd_solana_manifest_t const * manifest, + ulong epoch, + ulong * out_cnt ) { + + fd_epoch_stakes_t const * stakes = _find_epoch( manifest, epoch ); + fd_vote_accounts_t const * vaccs = &stakes->stakes.vote_accounts; + + ulong vote_acc_cnt = fd_vote_accounts_pair_t_map_size( vaccs->vote_accounts_pool, vaccs->vote_accounts_root ); + FD_LOG_NOTICE(( "vote_acc_cnt=%lu", vote_acc_cnt )); + fd_stake_weight_t * weights = fd_scratch_alloc( alignof(fd_stake_weight_t), vote_acc_cnt * sizeof(fd_stake_weight_t) ); + if( FD_UNLIKELY( !weights ) ) FD_LOG_ERR(( "fd_scratch_alloc() failed" )); + + ulong weight_cnt = fd_stake_weights_by_node( vaccs, weights ); + if( FD_UNLIKELY( weight_cnt==ULONG_MAX ) ) FD_LOG_ERR(( "fd_stake_weights_by_node() failed" )); + + *out_cnt = weight_cnt; + return weights; +} + +static int +action_epochs( fd_solana_manifest_t const * manifest ) { + fd_epoch_epoch_stakes_pair_t const * epochs = manifest->bank.epoch_stakes; + for( ulong i=0; i < manifest->bank.epoch_stakes_len; i++ ) + printf( "%lu\n", epochs[ i ].key ); + return 0; +} + +static int +action_nodes( fd_solana_manifest_t const * manifest, + ulong epoch ) { + + ulong weight_cnt; + fd_stake_weight_t * weights = _get_stake_weights( manifest, epoch, &weight_cnt ); + + for( ulong i=0UL; ikey.key, NULL, keyB58 ); + printf( "%s,%lu\n", keyB58, w->stake ); + } + + return 0; +} + +static int +action_leaders( fd_solana_manifest_t const * manifest, + ulong epoch ) { + + ulong weight_cnt; + fd_stake_weight_t * weights = _get_stake_weights( manifest, epoch, &weight_cnt ); + + fd_epoch_schedule_t const * sched = &manifest->bank.epoch_schedule; + ulong slot0 = fd_epoch_slot0 ( sched, epoch ); + ulong slot_cnt = fd_epoch_slot_cnt( sched, epoch ); + ulong sched_cnt = slot_cnt/FD_EPOCH_SLOTS_PER_ROTATION; + + void * leaders_mem = fd_scratch_alloc( fd_epoch_leaders_align(), fd_epoch_leaders_footprint( weight_cnt, sched_cnt ) ); + leaders_mem = fd_epoch_leaders_new( leaders_mem, epoch, slot0, slot_cnt, weight_cnt, weights ); + fd_epoch_leaders_t * leaders = fd_epoch_leaders_join( leaders_mem ); + FD_TEST( leaders ); + + fd_epoch_leaders_derive( leaders, weights, epoch ); + + ulong slot = slot0; + for( ulong i=0; ikey, NULL, keyB58 ); + for( ulong j=0; j