Line data Source code
1 : #include "fd_sysvar_stake_history.h"
2 : #include "fd_sysvar.h"
3 : #include "../fd_system_ids.h"
4 : #include "../fd_accdb_svm.h"
5 : #include "../../accdb/fd_accdb_sync.h"
6 : #include "fd_sysvar_rent.h"
7 :
8 : void
9 : fd_sysvar_stake_history_init( fd_bank_t * bank,
10 : fd_accdb_user_t * accdb,
11 : fd_funk_txn_xid_t const * xid,
12 24 : fd_capture_ctx_t * capture_ctx ) {
13 24 : uchar data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
14 24 : fd_memset( data, 0, sizeof(data) );
15 24 : fd_sysvar_account_update( bank, accdb, xid, capture_ctx, &fd_sysvar_stake_history_id, data, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
16 24 : }
17 :
18 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-rc.1/runtime/src/bank.rs#L2452-L2463 */
19 :
20 : void
21 : fd_sysvar_stake_history_update( fd_bank_t * bank,
22 : fd_accdb_user_t * accdb,
23 : fd_funk_txn_xid_t const * xid,
24 : fd_capture_ctx_t * capture_ctx,
25 162 : fd_stake_history_entry_t const * entry ) {
26 :
27 162 : fd_accdb_rw_t rw[1];
28 162 : fd_accdb_svm_update_t update[1];
29 162 : if( FD_UNLIKELY( !fd_accdb_svm_open_rw( accdb, bank, xid, rw, update, &fd_sysvar_stake_history_id, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ, FD_ACCDB_FLAG_CREATE ) ) ) {
30 0 : FD_LOG_ERR(( "state is missing stake history sysvar" ));
31 0 : }
32 :
33 162 : if( FD_UNLIKELY( !fd_accdb_ref_lamports( rw->ro ) ) ) {
34 : /* Initialize account if it did not exist */
35 0 : fd_accdb_ref_owner_set( rw, &fd_sysvar_owner_id );
36 0 : fd_accdb_ref_lamports_set( rw, fd_rent_exempt_minimum_balance( &bank->f.rent, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ) );
37 0 : fd_accdb_ref_data_sz_set( accdb, rw, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ, 0 );
38 : /* Now a valid StakeHistory sysvar with zero entries */
39 162 : } else {
40 : /* Sanity check existing state */
41 162 : if( FD_UNLIKELY( 0!=memcmp( fd_accdb_ref_owner( rw->ro ), &fd_sysvar_owner_id, sizeof(fd_pubkey_t) ) ) ) {
42 0 : FD_LOG_ERR(( "stake history sysvar not owned by sysvar owner" ));
43 0 : }
44 162 : }
45 :
46 162 : uchar * data = fd_accdb_ref_data ( rw );
47 162 : ulong data_sz = fd_accdb_ref_data_sz( rw->ro );
48 162 : if( FD_UNLIKELY( data_sz < 8UL ) ) {
49 0 : FD_LOG_ERR(( "invalid stake history sysvar" ));
50 0 : }
51 :
52 162 : ulong len = FD_LOAD( ulong, data );
53 162 : len = fd_ulong_min( len, FD_SYSVAR_STAKE_HISTORY_CAP );
54 162 : ulong min_sz = 8UL + len * sizeof(fd_stake_history_entry_t);
55 162 : if( FD_UNLIKELY( data_sz < min_sz ) ) {
56 0 : FD_LOG_ERR(( "invalid stake history sysvar: data_sz too small (%lu, required %lu)", data_sz, min_sz ));
57 0 : }
58 :
59 : /* https://github.com/anza-xyz/solana-sdk/blob/account%40v4.3.0/account/src/lib.rs#L618 */
60 162 : if( data_sz!=FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ) {
61 6 : fd_accdb_ref_data_sz_set( accdb, rw, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ, 0 );
62 6 : }
63 :
64 162 : fd_stake_history_entry_t * entries = fd_type_pun( data+8UL );
65 :
66 : /* https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L83 */
67 162 : ulong idx = 0UL;
68 162 : int found = 0;
69 162 : {
70 162 : ulong lo = 0UL;
71 162 : ulong hi = len;
72 273 : while( lo < hi ) {
73 114 : ulong mid = lo + (hi - lo) / 2UL;
74 114 : ulong probe_epoch = entries[mid].epoch;
75 114 : if( entry->epoch == probe_epoch ) {
76 3 : idx = mid;
77 3 : found = 1;
78 3 : break;
79 111 : } else if( entry->epoch > probe_epoch ) {
80 78 : hi = mid;
81 78 : } else {
82 33 : lo = mid + 1UL;
83 33 : }
84 114 : }
85 162 : if( !found ) idx = lo;
86 162 : }
87 :
88 : /* Ensure account is rent exempt
89 : https://github.com/anza-xyz/agave/blob/v4.0.0-rc.1/runtime/src/bank.rs#L5849-L5854 */
90 162 : ulong rent_min = fd_rent_exempt_minimum_balance( &bank->f.rent, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
91 162 : if( rent_min > fd_accdb_ref_lamports( rw->ro ) ) {
92 9 : fd_accdb_ref_lamports_set( rw, rent_min );
93 9 : }
94 :
95 : /* Insert new element */
96 162 : ulong new_len = fd_ulong_if( found, len, fd_ulong_min( len+1UL, FD_SYSVAR_STAKE_HISTORY_CAP ) );
97 162 : ulong used_sz = 8UL + new_len * sizeof(fd_stake_history_entry_t);
98 :
99 162 : if( found ) {
100 : /* https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L84 */
101 3 : entries[ idx ] = *entry;
102 159 : } else if( idx < FD_SYSVAR_STAKE_HISTORY_CAP ) {
103 : /* https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L85
104 : https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L87 */
105 156 : ulong shift_count = fd_ulong_min( len, FD_SYSVAR_STAKE_HISTORY_CAP-1UL ) - idx;
106 156 : memmove( &entries[ idx+1UL ], &entries[ idx ], shift_count * sizeof(fd_stake_history_entry_t) );
107 156 : entries[ idx ] = *entry;
108 156 : new_len = fd_ulong_min( len+1UL, FD_SYSVAR_STAKE_HISTORY_CAP );
109 156 : }
110 : /* else: idx == cap and not found - new entry would be truncated, drop */
111 :
112 : /* Zero trailing bytes (technically a no-op) */
113 162 : FD_TEST( used_sz <= FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
114 162 : fd_memset( data+used_sz, 0, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ-used_sz );
115 :
116 162 : FD_STORE( ulong, data, new_len );
117 162 : fd_accdb_svm_close_rw( accdb, bank, capture_ctx, rw, update );
118 162 : }
119 :
120 : int
121 : fd_sysvar_stake_history_validate( uchar const * data,
122 7569 : ulong sz ) {
123 7569 : if( FD_UNLIKELY( sz < 8UL ) ) return 0;
124 7569 : ulong len = FD_LOAD( ulong, data );
125 7569 : ulong min_sz;
126 7569 : if( FD_UNLIKELY( __builtin_umull_overflow( len, 32UL, &min_sz ) ) ) return 0;
127 7569 : if( FD_UNLIKELY( __builtin_uaddl_overflow( min_sz, 8UL, &min_sz ) ) ) return 0;
128 7569 : if( FD_UNLIKELY( sz < min_sz ) ) return 0;
129 7569 : return 1;
130 7569 : }
131 :
132 : fd_stake_history_t *
133 : fd_sysvar_stake_history_view( fd_stake_history_t * view,
134 : uchar const * data,
135 606 : ulong sz ) {
136 606 : if( FD_UNLIKELY( !fd_sysvar_stake_history_validate( data, sz ) ) ) return NULL;
137 606 : view->len = FD_LOAD( ulong, data );
138 606 : view->entries = fd_type_pun_const( data + 8UL );
139 606 : return view;
140 606 : }
141 :
142 : fd_stake_history_entry_t const *
143 : fd_sysvar_stake_history_query( fd_stake_history_t const * view,
144 255 : ulong epoch ) {
145 255 : if( FD_UNLIKELY( !view || !view->len ) ) return NULL;
146 66 : if( epoch > view->entries[0].epoch ) return NULL;
147 :
148 63 : ulong off = view->entries[0].epoch - epoch;
149 63 : if( off < view->len && view->entries[off].epoch == epoch ) {
150 24 : return &view->entries[off];
151 24 : }
152 :
153 39 : ulong lo = 0UL;
154 39 : ulong hi = view->len - 1UL;
155 165 : while( lo <= hi ) {
156 135 : ulong mid = lo + ( hi - lo ) / 2UL;
157 135 : if( view->entries[mid].epoch == epoch ) {
158 9 : return &view->entries[mid];
159 126 : } else if( view->entries[mid].epoch > epoch ) {
160 126 : lo = mid + 1UL;
161 126 : } else {
162 0 : if( mid == 0UL ) return NULL;
163 0 : hi = mid - 1UL;
164 0 : }
165 135 : }
166 30 : return NULL;
167 39 : }
|