Line data Source code
1 : #include "fd_sysvar.h"
2 : #include "fd_sysvar_clock.h"
3 : #include "fd_sysvar_epoch_schedule.h"
4 : #include "fd_sysvar_rent.h"
5 : #include "../fd_acc_mgr.h"
6 : #include "../fd_system_ids.h"
7 :
8 : /* https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/runtime/src/stake_weighted_timestamp.rs#L14 */
9 0 : #define MAX_ALLOWABLE_DRIFT_FAST ( 25 )
10 :
11 : /* https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/runtime/src/stake_weighted_timestamp.rs#L16 */
12 0 : #define MAX_ALLOWABLE_DRIFT_SLOW ( 150 )
13 :
14 : /* Do all intermediate calculations at nanosecond precision, to mirror Solana's behaviour. */
15 0 : #define NS_IN_S ((long)1e9)
16 :
17 : /* The target tick duration, derived from the target tick rate.
18 : https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/sdk/src/poh_config.rs#L32
19 : */
20 : #define DEFAULT_TARGET_TICK_DURATION_NS ( NS_IN_S / FD_SYSVAR_CLOCK_DEFAULT_HASHES_PER_TICK )
21 :
22 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2110-L2117 */
23 : static inline long
24 0 : unix_timestamp_from_genesis( fd_bank_t * bank ) {
25 : /* TODO: genesis_creation_time needs to be a long in the bank. */
26 0 : return fd_long_sat_add(
27 0 : (long)fd_bank_genesis_creation_time_get( bank ),
28 0 : (long)( fd_uint128_sat_mul( fd_bank_slot_get( bank ), fd_bank_ns_per_slot_get( bank ) ) / NS_IN_S ) );
29 0 : }
30 :
31 : void
32 : fd_sysvar_clock_write( fd_exec_slot_ctx_t * slot_ctx,
33 0 : fd_sol_sysvar_clock_t * clock ) {
34 0 : ulong sz = fd_sol_sysvar_clock_size( clock );
35 0 : uchar enc[sz];
36 0 : memset( enc, 0, sz );
37 0 : fd_bincode_encode_ctx_t ctx;
38 0 : ctx.data = enc;
39 0 : ctx.dataend = enc + sz;
40 0 : if( fd_sol_sysvar_clock_encode( clock, &ctx ) )
41 0 : FD_LOG_ERR(("fd_sol_sysvar_clock_encode failed"));
42 :
43 0 : fd_sysvar_account_update( slot_ctx, &fd_sysvar_clock_id, enc, sz );
44 0 : }
45 :
46 :
47 : fd_sol_sysvar_clock_t *
48 : fd_sysvar_clock_read( fd_funk_t * funk,
49 : fd_funk_txn_t * funk_txn,
50 0 : fd_sol_sysvar_clock_t * clock ) {
51 0 : FD_TXN_ACCOUNT_DECL( acc );
52 0 : int rc = fd_txn_account_init_from_funk_readonly( acc, &fd_sysvar_clock_id, funk, funk_txn );
53 0 : if( FD_UNLIKELY( rc!=FD_ACC_MGR_SUCCESS ) ) {
54 0 : return NULL;
55 0 : }
56 :
57 : /* This check is needed as a quirk of the fuzzer. If a sysvar account
58 : exists in the accounts database, but doesn't have any lamports,
59 : this means that the account does not exist. This wouldn't happen
60 : in a real execution environment. */
61 0 : if( FD_UNLIKELY( fd_txn_account_get_lamports( acc )==0UL ) ) {
62 0 : return NULL;
63 0 : }
64 :
65 0 : return fd_bincode_decode_static(
66 0 : sol_sysvar_clock, clock,
67 0 : fd_txn_account_get_data( acc ),
68 0 : fd_txn_account_get_data_len( acc ),
69 0 : &err );
70 0 : }
71 :
72 : void
73 0 : fd_sysvar_clock_init( fd_exec_slot_ctx_t * slot_ctx ) {
74 0 : long timestamp = unix_timestamp_from_genesis( slot_ctx->bank );
75 :
76 0 : fd_sol_sysvar_clock_t clock = {
77 0 : .slot = fd_bank_slot_get( slot_ctx->bank ),
78 0 : .epoch = 0,
79 0 : .epoch_start_timestamp = timestamp,
80 0 : .leader_schedule_epoch = 1,
81 0 : .unix_timestamp = timestamp,
82 0 : };
83 0 : fd_sysvar_clock_write( slot_ctx, &clock );
84 0 : }
85 :
86 0 : #define CIDX_T ulong
87 : #define VAL_T long
88 : struct stake_ts_ele {
89 : CIDX_T parent_cidx;
90 : CIDX_T left_cidx;
91 : CIDX_T right_cidx;
92 : CIDX_T prio_cidx;
93 : VAL_T timestamp;
94 : uint128 stake;
95 : };
96 :
97 : typedef struct stake_ts_ele stake_ts_ele_t;
98 :
99 : #define POOL_NAME stake_ts_pool
100 0 : #define POOL_T stake_ts_ele_t
101 : #define POOL_IDX_T CIDX_T
102 0 : #define POOL_NEXT parent_cidx
103 : #include "../../../util/tmpl/fd_pool.c"
104 :
105 0 : FD_FN_CONST static inline int valcmp (VAL_T a, VAL_T b) {
106 0 : int val = (a < b) ? -1 : 1;
107 0 : return (a == b) ? 0 : val;
108 0 : }
109 :
110 : #define TREAP_NAME stake_ts_treap
111 : #define TREAP_T stake_ts_ele_t
112 : #define TREAP_QUERY_T VAL_T
113 0 : #define TREAP_CMP(q,e) valcmp(q, e->timestamp)
114 0 : #define TREAP_LT(e0,e1) (((VAL_T)((e0)->timestamp)) < ((VAL_T)((e1)->timestamp)))
115 0 : #define TREAP_IDX_T CIDX_T
116 0 : #define TREAP_PARENT parent_cidx
117 0 : #define TREAP_LEFT left_cidx
118 0 : #define TREAP_RIGHT right_cidx
119 0 : #define TREAP_PRIO prio_cidx
120 : #define TREAP_IMPL_STYLE 0
121 : #include "../../../util/tmpl/fd_treap.c"
122 :
123 : /* get_timestamp_estimate calculates a timestamp estimate. Does not
124 : modify the slot context. Walks all cached vote accounts (from the
125 : "bank") and calculates a unix timestamp estimate. Returns the
126 : timestamp estimate. spad is used for scratch allocations (allocates
127 : a treap of size FD_SYSVAR_CLOCK_STAKE_WEIGHTS_MAX). Crashes the
128 : process with FD_LOG_ERR on failure (e.g. too many vote accounts).
129 :
130 : https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2563-L2601 */
131 : long
132 : get_timestamp_estimate( fd_bank_t * bank,
133 : fd_sol_sysvar_clock_t * clock,
134 0 : fd_spad_t * spad ) {
135 0 : FD_SPAD_FRAME_BEGIN( spad ) {
136 :
137 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
138 0 : ulong slot_duration = (ulong)fd_bank_ns_per_slot_get( bank );
139 0 : ulong current_slot = fd_bank_slot_get( bank );
140 :
141 : /* Set up a temporary treap, pool, and rng (required for treap prio).
142 : This is to establish a mapping between each vote timestamp and the
143 : amount of stake associated with each timestamp. */
144 0 : stake_ts_treap_t _treap[1];
145 0 : stake_ts_treap_t * treap = stake_ts_treap_join( stake_ts_treap_new( _treap, FD_RUNTIME_MAX_VOTE_ACCOUNTS ) );
146 0 : uchar * pool_mem = fd_spad_alloc( spad, stake_ts_pool_align(), stake_ts_pool_footprint( FD_RUNTIME_MAX_VOTE_ACCOUNTS ) );
147 0 : stake_ts_ele_t * pool = stake_ts_pool_join( stake_ts_pool_new( pool_mem, FD_RUNTIME_MAX_VOTE_ACCOUNTS ) );
148 0 : uint txn_cnt = (uint)fd_bank_transaction_count_get( bank );
149 :
150 0 : fd_rng_t _rng[1];
151 0 : fd_rng_t * rng = fd_rng_join( fd_rng_new( _rng, txn_cnt, 0UL ) );
152 :
153 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L41 */
154 0 : uint128 total_stake = 0UL;
155 :
156 : /* A timestamp estimate is calculated at every slot using the most
157 : recent vote states of voting validators. This estimated is based on
158 : a stake weighted median using the stake as of the end of epoch E-2
159 : if we are currently in epoch E. We do not count vote accounts that
160 : have not voted in an epoch's worth of slots (432k). */
161 0 : fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_query( bank );
162 0 : fd_vote_states_t const * vote_states_prev_prev = fd_bank_vote_states_prev_prev_locking_query( bank );
163 :
164 0 : fd_vote_states_iter_t iter_[1];
165 0 : for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( iter_, vote_states );
166 0 : !fd_vote_states_iter_done( iter );
167 0 : fd_vote_states_iter_next( iter ) ) {
168 0 : fd_vote_state_ele_t const * vote_state = fd_vote_states_iter_ele( iter );
169 0 : fd_vote_state_ele_t const * vote_state_prev = fd_vote_states_query_const( vote_states_prev_prev, &vote_state->vote_account );
170 0 : if( !vote_state_prev ) {
171 : /* Don't count vote accounts that didn't have stake at the end of
172 : epoch E-2. */
173 0 : continue;
174 0 : }
175 0 : ulong vote_stake = vote_state_prev->stake;
176 0 : ulong vote_timestamp = (ulong)vote_state->last_vote_timestamp;
177 0 : ulong vote_slot = vote_state->last_vote_slot;
178 :
179 : /* https://github.com/anza-xyz/agave/blob/v3.0.0/runtime/src/bank.rs#L2445 */
180 0 : ulong slot_delta;
181 0 : int err = fd_ulong_checked_sub( current_slot, vote_slot, &slot_delta );
182 0 : if( FD_UNLIKELY( err ) ) {
183 0 : continue;
184 0 : }
185 :
186 : /* Don't count vote accounts that haven't voted in the past 432k
187 : slots (length of an epoch).
188 : https://github.com/anza-xyz/agave/blob/v3.0.0/runtime/src/bank.rs#L2446-L2447 */
189 0 : if( slot_delta>epoch_schedule->slots_per_epoch ) {
190 0 : continue;
191 0 : }
192 :
193 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L44-L45 */
194 0 : ulong offset = fd_ulong_sat_mul(slot_duration, slot_delta);
195 0 : long estimate = (long)vote_timestamp + (long)(offset / NS_IN_S);
196 :
197 : /* Get the vote account stake and upsert it to the treap.
198 : https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L46-L53 */
199 0 : ulong treap_idx = stake_ts_treap_idx_query( treap, estimate, pool );
200 0 : if ( FD_LIKELY( treap_idx < ULONG_MAX ) ) {
201 0 : pool[ treap_idx ].stake += vote_stake;
202 0 : } else {
203 0 : if( FD_UNLIKELY( stake_ts_pool_free( pool )==0UL ) ){
204 0 : FD_LOG_ERR(( "stake_ts_pool is empty" ));
205 0 : }
206 0 : ulong idx = stake_ts_pool_idx_acquire( pool );
207 0 : pool[ idx ].prio_cidx = fd_rng_ulong( rng );
208 0 : pool[ idx ].timestamp = estimate;
209 0 : pool[ idx ].stake = vote_stake;
210 0 : stake_ts_treap_idx_insert( treap, idx, pool );
211 0 : }
212 :
213 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L54 */
214 0 : total_stake += vote_stake;
215 0 : }
216 0 : fd_bank_vote_states_end_locking_query( bank );
217 0 : fd_bank_vote_states_prev_prev_end_locking_query( bank );
218 :
219 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L56-L58 */
220 0 : if( FD_UNLIKELY( total_stake==0UL ) ) {
221 0 : return 0L;
222 0 : }
223 :
224 : /* Populate estimate with the stake-weighted median timestamp.
225 : https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L59-L68 */
226 0 : uint128 stake_accumulator = 0;
227 0 : long estimate = 0L;
228 0 : for( stake_ts_treap_fwd_iter_t iter = stake_ts_treap_fwd_iter_init( treap, pool );
229 0 : !stake_ts_treap_fwd_iter_done( iter );
230 0 : iter = stake_ts_treap_fwd_iter_next( iter, pool ) ) {
231 0 : ulong idx = stake_ts_treap_fwd_iter_idx( iter );
232 0 : stake_accumulator = fd_uint128_sat_add( stake_accumulator, pool[ idx ].stake );
233 0 : if( stake_accumulator>(total_stake/2UL) ) {
234 0 : estimate = pool[ idx ].timestamp;
235 0 : break;
236 0 : }
237 0 : }
238 :
239 0 : FD_LOG_DEBUG(( "stake weighted timestamp: %ld total stake %lu", estimate, (ulong)total_stake ));
240 :
241 0 : int const fix_estimate_into_u64 = FD_FEATURE_ACTIVE_BANK( bank, warp_timestamp_again );
242 :
243 : /* Bound estimate by `max_allowable_drift` since the start of the epoch
244 : https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L69-L99 */
245 0 : ulong epoch_start_slot = fd_epoch_slot0( epoch_schedule, clock->epoch );
246 0 : long epoch_start_timestamp = clock->epoch_start_timestamp;
247 :
248 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L71-L72 */
249 0 : ulong poh_estimate_offset = fd_ulong_sat_mul( slot_duration, fd_ulong_sat_sub( current_slot, epoch_start_slot ) );
250 :
251 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L73-L77 */
252 0 : ulong estimate_offset;
253 0 : if( fix_estimate_into_u64 ) {
254 0 : estimate_offset = fd_ulong_sat_mul( NS_IN_S, fd_ulong_sat_sub( (ulong)estimate, (ulong)epoch_start_timestamp ) );
255 0 : } else {
256 0 : estimate_offset = fd_ulong_sat_mul( NS_IN_S, (ulong)fd_long_sat_sub( estimate, epoch_start_timestamp ) );
257 0 : }
258 :
259 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L78-L81 */
260 0 : ulong max_allowable_drift_fast = fd_ulong_sat_mul( poh_estimate_offset, MAX_ALLOWABLE_DRIFT_FAST ) / 100UL;
261 0 : ulong max_allowable_drift_slow = fd_ulong_sat_mul( poh_estimate_offset, MAX_ALLOWABLE_DRIFT_SLOW ) / 100UL;
262 0 : FD_LOG_DEBUG(( "poh offset %lu estimate %lu fast %lu slow %lu", poh_estimate_offset, estimate_offset, max_allowable_drift_fast, max_allowable_drift_slow ));
263 :
264 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L82-L98 */
265 0 : if( estimate_offset>poh_estimate_offset && fd_ulong_sat_sub(estimate_offset, poh_estimate_offset)>max_allowable_drift_slow ) {
266 0 : estimate = fd_long_sat_add(
267 0 : epoch_start_timestamp,
268 0 : fd_long_sat_add( (long)poh_estimate_offset / NS_IN_S, (long)max_allowable_drift_slow / NS_IN_S ) );
269 0 : } else if( estimate_offset<poh_estimate_offset && fd_ulong_sat_sub(poh_estimate_offset, estimate_offset)>max_allowable_drift_fast ) {
270 0 : estimate = fd_long_sat_sub(
271 0 : fd_long_sat_add( epoch_start_timestamp, (long)poh_estimate_offset / NS_IN_S ),
272 0 : (long)max_allowable_drift_fast / NS_IN_S );
273 0 : }
274 :
275 0 : return estimate;
276 :
277 0 : } FD_SPAD_FRAME_END;
278 0 : }
279 :
280 : /* TODO: This function should be called from genesis bootup as well with
281 : parent_epoch = NULL
282 : https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2158-L2215 */
283 : void
284 : fd_sysvar_clock_update( fd_exec_slot_ctx_t * slot_ctx,
285 : fd_spad_t * spad,
286 0 : ulong const * parent_epoch ) {
287 0 : fd_sol_sysvar_clock_t clock_[1];
288 0 : fd_sol_sysvar_clock_t * clock = fd_sysvar_clock_read( slot_ctx->funk, slot_ctx->funk_txn, clock_ );
289 0 : if( FD_UNLIKELY( !clock ) ) FD_LOG_ERR(( "fd_sysvar_clock_read failed" ));
290 :
291 0 : fd_bank_t * bank = slot_ctx->bank;
292 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
293 0 : ulong current_slot = fd_bank_slot_get( bank );
294 0 : ulong current_epoch = fd_slot_to_epoch( epoch_schedule, current_slot, NULL );
295 :
296 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2159 */
297 0 : long unix_timestamp = clock->unix_timestamp;
298 :
299 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2175 */
300 0 : long ancestor_timestamp = clock->unix_timestamp;
301 :
302 : /* TODO: Are we handling slot 0 correctly?
303 : https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2176-L2183 */
304 0 : long timestamp_estimate = get_timestamp_estimate( bank, clock, spad );
305 :
306 : /* If the timestamp was successfully calculated, use it. It not keep the old one. */
307 0 : if( FD_LIKELY( timestamp_estimate!=0L ) ) {
308 0 : unix_timestamp = timestamp_estimate;
309 :
310 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2180-L2182 */
311 0 : if( timestamp_estimate<ancestor_timestamp ) {
312 0 : unix_timestamp = ancestor_timestamp;
313 0 : }
314 0 : }
315 :
316 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2191-L2197 */
317 0 : long epoch_start_timestamp = ( parent_epoch!=NULL && *parent_epoch!=current_epoch ) ?
318 0 : unix_timestamp :
319 0 : clock->epoch_start_timestamp;
320 :
321 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2198-L2201 */
322 0 : if( FD_UNLIKELY( current_slot==0UL ) ) {
323 0 : long timestamp_from_genesis = unix_timestamp_from_genesis( bank );
324 0 : unix_timestamp = timestamp_from_genesis;
325 0 : epoch_start_timestamp = timestamp_from_genesis;
326 0 : }
327 :
328 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2202-L2208 */
329 0 : *clock = (fd_sol_sysvar_clock_t){
330 0 : .slot = current_slot,
331 0 : .epoch_start_timestamp = epoch_start_timestamp,
332 0 : .epoch = current_epoch,
333 0 : .leader_schedule_epoch = fd_slot_to_leader_schedule_epoch( epoch_schedule, current_slot ),
334 0 : .unix_timestamp = unix_timestamp,
335 0 : };
336 :
337 : /* https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/bank.rs#L2209-L2214 */
338 0 : fd_sysvar_clock_write( slot_ctx, clock );
339 0 : }
|