Line data Source code
1 : #include "fd_stakes.h"
2 : #include "../runtime/fd_acc_mgr.h"
3 : #include "../runtime/fd_bank.h"
4 : #include "../runtime/fd_system_ids.h"
5 : #include "../runtime/program/fd_stake_program.h"
6 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
7 : #include "fd_stake_delegations.h"
8 :
9 : ulong
10 : fd_stake_weights_by_node( fd_vote_states_t const * vote_states,
11 0 : fd_vote_stake_weight_t * weights ) {
12 :
13 0 : ulong weights_cnt = 0;
14 0 : fd_vote_states_iter_t iter_[1];
15 0 : for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( iter_, vote_states ); !fd_vote_states_iter_done( iter ); fd_vote_states_iter_next( iter ) ) {
16 0 : fd_vote_state_ele_t const * vote_state = fd_vote_states_iter_ele( iter );
17 0 : if( FD_UNLIKELY( !vote_state->stake ) ) continue;
18 :
19 0 : fd_memcpy( weights[ weights_cnt ].vote_key.uc, &vote_state->vote_account, sizeof(fd_pubkey_t) );
20 0 : fd_memcpy( weights[ weights_cnt ].id_key.uc, &vote_state->node_account, sizeof(fd_pubkey_t) );
21 0 : weights[ weights_cnt ].stake = vote_state->stake;
22 0 : weights_cnt++;
23 0 : }
24 0 : sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
25 0 : return weights_cnt;
26 0 : }
27 :
28 : static void
29 : compute_stake_delegations( fd_bank_t * bank,
30 : fd_stake_delegations_t const * stake_delegations,
31 : fd_stake_history_t const * history,
32 0 : ulong * new_rate_activation_epoch ) {
33 0 : ulong epoch = fd_bank_epoch_get( bank );
34 :
35 0 : ulong total_stake = 0UL;
36 :
37 0 : fd_vote_states_t * vote_states = fd_bank_vote_states_locking_modify( bank );
38 0 : if( FD_UNLIKELY( !vote_states ) ) {
39 0 : FD_LOG_CRIT(( "vote_states is NULL" ));
40 0 : }
41 :
42 : /* Reset the vote stakes so we can re-compute them based on the most
43 : current stake delegation values. */
44 0 : fd_vote_states_reset_stakes( vote_states );
45 :
46 0 : fd_stake_delegations_iter_t iter_[1];
47 0 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
48 0 : !fd_stake_delegations_iter_done( iter );
49 0 : fd_stake_delegations_iter_next( iter ) ) {
50 0 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
51 :
52 : // Skip any delegations that are not in the delegation pool
53 :
54 0 : fd_delegation_t delegation = {
55 0 : .voter_pubkey = stake_delegation->vote_account,
56 0 : .stake = stake_delegation->stake,
57 0 : .deactivation_epoch = stake_delegation->deactivation_epoch,
58 0 : .activation_epoch = stake_delegation->activation_epoch,
59 0 : .warmup_cooldown_rate = stake_delegation->warmup_cooldown_rate,
60 0 : };
61 :
62 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating(
63 0 : &delegation,
64 0 : epoch,
65 0 : history,
66 0 : new_rate_activation_epoch );
67 :
68 0 : fd_vote_state_ele_t * vote_state = fd_vote_states_query( vote_states, &stake_delegation->vote_account );
69 0 : if( FD_LIKELY( vote_state ) ) {
70 0 : total_stake += new_entry.effective;
71 0 : vote_state->stake += new_entry.effective;
72 0 : }
73 0 : }
74 :
75 0 : fd_bank_total_epoch_stake_set( bank, total_stake );
76 :
77 0 : fd_bank_vote_states_end_locking_modify( bank );
78 0 : }
79 :
80 : /* Refresh vote accounts.
81 :
82 : This updates the epoch bank stakes vote_accounts cache - that is, the total amount
83 : of delegated stake each vote account has, using the current delegation values from inside each
84 : stake account. Contrary to the Agave equivalent, it also merges the stakes cache vote accounts with the
85 : new vote account keys from this epoch.
86 :
87 : https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L562 */
88 : void
89 : fd_refresh_vote_accounts( fd_bank_t * bank,
90 : fd_stake_delegations_t const * stake_delegations,
91 : fd_stake_history_t const * history,
92 0 : ulong * new_rate_activation_epoch ) {
93 :
94 0 : compute_stake_delegations(
95 0 : bank,
96 0 : stake_delegations,
97 0 : history,
98 0 : new_rate_activation_epoch );
99 0 : }
100 :
101 : static void
102 : accumulate_stake_cache_delegations( fd_stake_delegations_t const * stake_delegations,
103 : fd_stake_history_t const * history,
104 : ulong * new_rate_activation_epoch,
105 : fd_stake_history_entry_t * accumulator,
106 0 : ulong epoch ) {
107 :
108 0 : ulong effective = 0UL;
109 0 : ulong activating = 0UL;
110 0 : ulong deactivating = 0UL;
111 :
112 0 : fd_stake_delegations_iter_t iter_[1];
113 0 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
114 0 : !fd_stake_delegations_iter_done( iter );
115 0 : fd_stake_delegations_iter_next( iter ) ) {
116 0 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
117 :
118 0 : fd_delegation_t delegation = {
119 0 : .voter_pubkey = stake_delegation->vote_account,
120 0 : .stake = stake_delegation->stake,
121 0 : .activation_epoch = stake_delegation->activation_epoch,
122 0 : .deactivation_epoch = stake_delegation->deactivation_epoch,
123 0 : .warmup_cooldown_rate = stake_delegation->warmup_cooldown_rate,
124 0 : };
125 :
126 0 : fd_stake_history_entry_t new_entry = fd_stake_activating_and_deactivating(
127 0 : &delegation,
128 0 : epoch,
129 0 : history,
130 0 : new_rate_activation_epoch );
131 0 : effective += new_entry.effective;
132 0 : activating += new_entry.activating;
133 0 : deactivating += new_entry.deactivating;
134 0 : }
135 :
136 0 : accumulator->effective += effective;
137 0 : accumulator->activating += activating;
138 0 : accumulator->deactivating += deactivating;
139 :
140 0 : }
141 :
142 : /* Accumulates information about epoch stakes into `temp_info`, which is a temporary cache
143 : used to save intermediate state about stake and vote accounts to avoid them from having to
144 : be recomputed on every access, especially at the epoch boundary. Also collects stats in `accumulator` */
145 : void
146 : fd_accumulate_stake_infos( ulong epoch,
147 : fd_stake_delegations_t const * stake_delegations,
148 : fd_stake_history_t const * history,
149 : ulong * new_rate_activation_epoch,
150 0 : fd_stake_history_entry_t * accumulator ) {
151 :
152 0 : accumulate_stake_cache_delegations(
153 0 : stake_delegations,
154 0 : history,
155 0 : new_rate_activation_epoch,
156 0 : accumulator,
157 0 : epoch );
158 :
159 0 : }
160 :
161 : /* https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L169 */
162 : void
163 : fd_stakes_activate_epoch( fd_bank_t * bank,
164 : fd_funk_t * funk,
165 : fd_funk_txn_xid_t const * xid,
166 : fd_capture_ctx_t * capture_ctx,
167 : fd_stake_delegations_t const * stake_delegations,
168 : ulong * new_rate_activation_epoch,
169 0 : fd_spad_t * runtime_spad ) {
170 :
171 : /* Current stake delegations: list of all current delegations in stake_delegations
172 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L180 */
173 : /* Add a new entry to the Stake History sysvar for the previous epoch
174 : https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/stakes.rs#L181-L192 */
175 :
176 0 : fd_stake_history_t const * history = fd_sysvar_stake_history_read( funk, xid, runtime_spad );
177 0 : if( FD_UNLIKELY( !history ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
178 :
179 0 : fd_stake_history_entry_t accumulator = {
180 0 : .effective = 0UL,
181 0 : .activating = 0UL,
182 0 : .deactivating = 0UL
183 0 : };
184 :
185 : /* Accumulate stats for stake accounts */
186 0 : fd_accumulate_stake_infos(
187 0 : fd_bank_epoch_get( bank ),
188 0 : stake_delegations,
189 0 : history,
190 0 : new_rate_activation_epoch,
191 0 : &accumulator );
192 :
193 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/runtime/src/stakes.rs#L359 */
194 0 : fd_epoch_stake_history_entry_pair_t new_elem = {
195 0 : .epoch = fd_bank_epoch_get( bank ),
196 0 : .entry = {
197 0 : .effective = accumulator.effective,
198 0 : .activating = accumulator.activating,
199 0 : .deactivating = accumulator.deactivating
200 0 : }
201 0 : };
202 :
203 0 : fd_sysvar_stake_history_update( bank, funk, xid, capture_ctx, &new_elem, runtime_spad );
204 :
205 0 : }
206 :
207 : int
208 : write_stake_state( fd_txn_account_t * stake_acc_rec,
209 0 : fd_stake_state_v2_t * stake_state ) {
210 :
211 0 : ulong encoded_stake_state_size = fd_stake_state_v2_size(stake_state);
212 :
213 0 : fd_bincode_encode_ctx_t ctx = {
214 0 : .data = fd_txn_account_get_data_mut( stake_acc_rec ),
215 0 : .dataend = fd_txn_account_get_data_mut( stake_acc_rec ) + encoded_stake_state_size,
216 0 : };
217 0 : if( FD_UNLIKELY( fd_stake_state_v2_encode( stake_state, &ctx ) != FD_BINCODE_SUCCESS ) ) {
218 0 : FD_LOG_ERR(( "fd_stake_state_encode failed" ));
219 0 : }
220 :
221 0 : return 0;
222 0 : }
223 :
224 : /* Removes stake delegation from stakes */
225 : static void
226 : fd_stakes_remove_stake_delegation( fd_txn_account_t * stake_account,
227 0 : fd_bank_t * bank ) {
228 :
229 0 : fd_stake_delegations_t * stake_delegations_delta = fd_bank_stake_delegations_delta_locking_modify( bank );
230 0 : if( FD_UNLIKELY( !stake_delegations_delta ) ) {
231 0 : FD_LOG_CRIT(( "unable to retrieve join to stake delegation delta" ));
232 0 : }
233 :
234 0 : fd_stake_delegations_remove( stake_delegations_delta, stake_account->pubkey );
235 :
236 0 : fd_bank_stake_delegations_delta_end_locking_modify( bank );
237 0 : }
238 :
239 : /* Updates stake delegation in epoch stakes */
240 : static void
241 : fd_stakes_upsert_stake_delegation( fd_txn_account_t * stake_account,
242 0 : fd_bank_t * bank ) {
243 :
244 0 : fd_stake_delegations_t * stake_delegations_delta = fd_bank_stake_delegations_delta_locking_modify( bank );
245 0 : if( FD_UNLIKELY( !stake_delegations_delta ) ) {
246 0 : FD_LOG_CRIT(( "unable to retrieve join to stake delegation delta" ));
247 0 : }
248 :
249 0 : fd_stake_state_v2_t stake_state;
250 0 : int err = fd_stake_get_state( stake_account, &stake_state );
251 0 : if( FD_UNLIKELY( err != 0 ) ) {
252 0 : FD_LOG_WARNING(( "Failed to get stake state" ));
253 0 : fd_bank_stake_delegations_delta_end_locking_modify( bank );
254 0 : return;
255 0 : }
256 :
257 0 : if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
258 0 : FD_LOG_WARNING(( "Not a valid stake" ));
259 0 : fd_bank_stake_delegations_delta_end_locking_modify( bank );
260 0 : return;
261 0 : }
262 :
263 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake==0UL ) ) {
264 0 : FD_LOG_WARNING(( "Stake is empty" ));
265 0 : fd_bank_stake_delegations_delta_end_locking_modify( bank );
266 0 : return;
267 0 : }
268 :
269 0 : fd_stake_delegations_update(
270 0 : stake_delegations_delta,
271 0 : stake_account->pubkey,
272 0 : &stake_state.inner.stake.stake.delegation.voter_pubkey,
273 0 : stake_state.inner.stake.stake.delegation.stake,
274 0 : stake_state.inner.stake.stake.delegation.activation_epoch,
275 0 : stake_state.inner.stake.stake.delegation.deactivation_epoch,
276 0 : stake_state.inner.stake.stake.credits_observed,
277 0 : stake_state.inner.stake.stake.delegation.warmup_cooldown_rate );
278 :
279 0 : fd_bank_stake_delegations_delta_end_locking_modify( bank );
280 0 : }
281 :
282 : void
283 : fd_update_stake_delegation( fd_txn_account_t * stake_account,
284 0 : fd_bank_t * bank ) {
285 :
286 0 : int is_empty = fd_txn_account_get_lamports( stake_account )==0UL;
287 0 : int is_uninit = 1;
288 0 : if( fd_txn_account_get_data_len( stake_account )>=4UL ) {
289 0 : uint prefix = FD_LOAD( uint, fd_txn_account_get_data( stake_account ) );
290 0 : is_uninit = ( prefix==fd_stake_state_v2_enum_uninitialized );
291 0 : }
292 :
293 0 : if( is_empty || is_uninit ) {
294 0 : fd_stakes_remove_stake_delegation( stake_account, bank );
295 0 : } else {
296 0 : fd_stakes_upsert_stake_delegation( stake_account, bank );
297 0 : }
298 0 : }
|