Line data Source code
1 : #include <stdio.h>
2 : #include "../fd_acc_mgr.h"
3 : #include "../fd_hashes.h"
4 : #include "fd_sysvar.h"
5 : #include "../fd_runtime.h"
6 : #include "../fd_system_ids.h"
7 : #include "../context/fd_exec_slot_ctx.h"
8 :
9 0 : #define FD_RECENT_BLOCKHASHES_ACCOUNT_MAX_SIZE sizeof(ulong) + FD_RECENT_BLOCKHASHES_MAX_ENTRIES * (sizeof(fd_hash_t) + sizeof(ulong))
10 :
11 : // run --ledger /home/jsiegel/test-ledger --db /home/jsiegel/funk --cmd accounts --accounts /home/jsiegel/test-ledger/accounts/ --pages 15 --index-max 120000000 --start-slot 2 --end-slot 2 --start-id 35 --end-id 37
12 : // run --ledger /home/jsiegel/test-ledger --db /home/jsiegel/funk --cmd replay --pages 15 --index-max 120000000 --start-slot 0 --end-slot 3
13 :
14 : // {meta = {write_version_obsolete = 137,
15 : // data_len = 6008, pubkey = "\006\247\325\027\031,V\216\340\212\204_sҗ\210\317\003\\1E\262\032\263D\330\006.\251@\000"}, info = {lamports = 42706560, rent_epoch = 0, owner = "\006\247\325\027\030u\367)\307=\223@\217!a \006~،v\340\214(\177\301\224`\000\000\000", executable = 0 '\000', padding = "K\000\f\376\177\000"}, hash = {value = "\302Q\316\035qTY\347\352]\260\335\213\224R\227ԯ\366R\273\063H\345֑c\377\207/k\275"}}
16 :
17 : // owner: Sysvar1111111111111111111111111111111111111 pubkey: SysvarRecentB1ockHashes11111111111111111111 hash: E5YSehyvJ7xXcNnQjWCH9UhMJ1dxDBJ1RuuPh1Y3RZgg file: /home/jsiegel/test-ledger/accounts//2.37
18 : // {blockhash = JCidNXtcMXMWQwMDM3ZQq5pxaw3hQpNbeHg1KcstjuF4, fee_calculator={lamports_per_signature = 5000}}
19 : // {blockhash = GQN3oV8G1Ra3GCX76dE1YYJ6UjMyDreNCEWM4tZ39zj1, fee_calculator={lamports_per_signature = 5000}}
20 : // {blockhash = Ha5DVgnD1xSA8oQc337jtA3atEfQ4TFX1ajeZG1Y2tUx, fee_calculator={lamports_per_signature = 0}}
21 :
22 : /* Skips fd_types encoding preflight checks and directly serializes the blockhash queue into a buffer representing
23 : account data for the recent blockhashes sysvar. */
24 :
25 : static void
26 0 : encode_rbh_from_blockhash_queue( fd_exec_slot_ctx_t * slot_ctx, uchar * enc ) {
27 0 : fd_block_hash_queue_global_t const * bhq = fd_bank_block_hash_queue_query( slot_ctx->bank );
28 :
29 0 : fd_hash_hash_age_pair_t_mapnode_t * ages_pool = fd_block_hash_queue_ages_pool_join( bhq );
30 0 : fd_hash_hash_age_pair_t_mapnode_t * ages_root = fd_block_hash_queue_ages_root_join( bhq );
31 :
32 0 : ulong queue_sz = fd_hash_hash_age_pair_t_map_size( ages_pool, ages_root );
33 0 : ulong hashes_len = fd_ulong_min( queue_sz, FD_RECENT_BLOCKHASHES_MAX_ENTRIES );
34 0 : fd_memcpy( enc, &hashes_len, sizeof(ulong) );
35 0 : enc += sizeof(ulong);
36 :
37 : /* Iterate over blockhash queue and encode the recent blockhashes.
38 : We can do direct memcpying and avoid redundant checks from fd_types
39 : encoders since the enc buffer is already sized out to the
40 : worst-case bound. */
41 0 : fd_hash_hash_age_pair_t_mapnode_t const * nn;
42 0 : for( fd_hash_hash_age_pair_t_mapnode_t const * n = fd_hash_hash_age_pair_t_map_minimum_const( ages_pool, ages_root ); n; n = nn ) {
43 0 : nn = fd_hash_hash_age_pair_t_map_successor_const( ages_pool, n );
44 0 : ulong enc_idx = bhq->last_hash_index - n->elem.val.hash_index;
45 0 : if( enc_idx>=hashes_len ) {
46 0 : continue;
47 0 : }
48 0 : fd_hash_t hash = n->elem.key;
49 0 : ulong lps = n->elem.val.fee_calculator.lamports_per_signature;
50 :
51 0 : fd_memcpy( enc + enc_idx * (FD_HASH_FOOTPRINT + sizeof(ulong)), &hash, FD_HASH_FOOTPRINT );
52 0 : fd_memcpy( enc + enc_idx * (FD_HASH_FOOTPRINT + sizeof(ulong)) + sizeof(fd_hash_t), &lps, sizeof(ulong) );
53 0 : }
54 0 : }
55 :
56 : // https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/sdk/program/src/fee_calculator.rs#L110
57 : void
58 : fd_sysvar_recent_hashes_init( fd_exec_slot_ctx_t * slot_ctx,
59 0 : fd_spad_t * runtime_spad ) {
60 :
61 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
62 :
63 0 : if( slot_ctx->slot != 0 ) {
64 0 : return;
65 0 : }
66 :
67 0 : ulong sz = FD_RECENT_BLOCKHASHES_ACCOUNT_MAX_SIZE;
68 0 : uchar * enc = fd_spad_alloc( runtime_spad, FD_SPAD_ALIGN, sz );
69 0 : fd_memset( enc, 0, sz );
70 0 : encode_rbh_from_blockhash_queue( slot_ctx, enc );
71 0 : fd_sysvar_set( slot_ctx->bank, slot_ctx->funk, slot_ctx->funk_txn, &fd_sysvar_owner_id, &fd_sysvar_recent_block_hashes_id, enc, sz, slot_ctx->slot );
72 :
73 0 : } FD_SPAD_FRAME_END;
74 0 : }
75 :
76 : // https://github.com/anza-xyz/agave/blob/e8750ba574d9ac7b72e944bc1227dc7372e3a490/accounts-db/src/blockhash_queue.rs#L113
77 : static void
78 0 : register_blockhash( fd_exec_slot_ctx_t * slot_ctx, fd_hash_t const * hash ) {
79 :
80 0 : fd_block_hash_queue_global_t * bhq = fd_bank_block_hash_queue_modify( slot_ctx->bank );
81 0 : fd_hash_hash_age_pair_t_mapnode_t * ages_pool = fd_block_hash_queue_ages_pool_join( bhq );
82 0 : fd_hash_hash_age_pair_t_mapnode_t * ages_root = fd_block_hash_queue_ages_root_join( bhq );
83 0 : bhq->last_hash_index++;
84 0 : if( fd_hash_hash_age_pair_t_map_size( ages_pool, ages_root ) >= bhq->max_age ) {
85 0 : fd_hash_hash_age_pair_t_mapnode_t * nn;
86 0 : for( fd_hash_hash_age_pair_t_mapnode_t * n = fd_hash_hash_age_pair_t_map_minimum( ages_pool, ages_root ); n; n = nn ) {
87 0 : nn = fd_hash_hash_age_pair_t_map_successor( ages_pool, n );
88 : /* NOTE: Yes, this check is incorrect. It should be >= which caps the blockhash queue at max_age
89 : entries, but instead max_age + 1 entries are allowed to exist in the queue at once. This mimics
90 : Agave to stay conformant with their implementation.
91 : https://github.com/anza-xyz/agave/blob/e8750ba574d9ac7b72e944bc1227dc7372e3a490/accounts-db/src/blockhash_queue.rs#L109 */
92 0 : if( bhq->last_hash_index - n->elem.val.hash_index > bhq->max_age ) {
93 0 : fd_hash_hash_age_pair_t_map_remove( ages_pool, &ages_root, n );
94 0 : fd_hash_hash_age_pair_t_map_release( ages_pool, n );
95 0 : }
96 0 : }
97 0 : }
98 :
99 0 : fd_hash_hash_age_pair_t_mapnode_t * node = fd_hash_hash_age_pair_t_map_acquire( ages_pool );
100 0 : node->elem = (fd_hash_hash_age_pair_t){
101 0 : .key = *hash,
102 0 : .val = (fd_hash_age_t){ .hash_index = bhq->last_hash_index, .fee_calculator = (fd_fee_calculator_t){ .lamports_per_signature = fd_bank_lamports_per_signature_get( slot_ctx->bank ) }, .timestamp = (ulong)fd_log_wallclock() }
103 0 : };
104 : // https://github.com/anza-xyz/agave/blob/e8750ba574d9ac7b72e944bc1227dc7372e3a490/accounts-db/src/blockhash_queue.rs#L121-L128
105 0 : fd_hash_hash_age_pair_t_map_insert( ages_pool, &ages_root, node );
106 : // https://github.com/anza-xyz/agave/blob/e8750ba574d9ac7b72e944bc1227dc7372e3a490/accounts-db/src/blockhash_queue.rs#L130
107 0 : fd_hash_t * last_hash = fd_block_hash_queue_last_hash_join( bhq );
108 0 : fd_memcpy( last_hash, hash, sizeof(fd_hash_t) );
109 :
110 0 : fd_block_hash_queue_ages_pool_update( bhq, ages_pool );
111 0 : fd_block_hash_queue_ages_root_update( bhq, ages_root );
112 0 : }
113 :
114 : /* This implementation is more consistent with Agave's bank implementation for updating the block hashes sysvar:
115 : 1. Update the block hash queue with the latest poh
116 : 2. Take the first 150 blockhashes from the queue (or fewer if there are)
117 : 3. Manually serialize the recent blockhashes
118 : 4. Set the sysvar account with the new data */
119 : void
120 0 : fd_sysvar_recent_hashes_update( fd_exec_slot_ctx_t * slot_ctx, fd_spad_t * runtime_spad ) {
121 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
122 : /* Update the blockhash queue */
123 :
124 0 : register_blockhash( slot_ctx, fd_bank_poh_query( slot_ctx->bank ) );
125 :
126 : /* Derive the new sysvar recent blockhashes from the blockhash queue */
127 0 : ulong sz = FD_RECENT_BLOCKHASHES_ACCOUNT_MAX_SIZE;
128 0 : uchar * enc = fd_spad_alloc( runtime_spad, FD_SPAD_ALIGN, sz );
129 0 : uchar * enc_start = enc;
130 0 : fd_memset( enc, 0, sz );
131 :
132 : /* Encode the recent blockhashes */
133 0 : encode_rbh_from_blockhash_queue( slot_ctx, enc );
134 :
135 : /* Set the sysvar from the encoded data */
136 0 : fd_sysvar_set( slot_ctx->bank,
137 0 : slot_ctx->funk,
138 0 : slot_ctx->funk_txn,
139 0 : &fd_sysvar_owner_id,
140 0 : &fd_sysvar_recent_block_hashes_id,
141 0 : enc_start,
142 0 : sz,
143 0 : slot_ctx->slot );
144 0 : } FD_SPAD_FRAME_END;
145 0 : }
146 :
147 : fd_recent_block_hashes_global_t *
148 0 : fd_sysvar_recent_hashes_read( fd_funk_t * funk, fd_funk_txn_t * funk_txn, fd_spad_t * spad ) {
149 0 : FD_TXN_ACCOUNT_DECL( acc );
150 0 : int err = fd_txn_account_init_from_funk_readonly( acc, &fd_sysvar_recent_block_hashes_id, funk, funk_txn );
151 0 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) )
152 0 : return NULL;
153 :
154 0 : fd_bincode_decode_ctx_t ctx = {
155 0 : .data = acc->vt->get_data( acc ),
156 0 : .dataend = acc->vt->get_data( acc ) + acc->vt->get_data_len( acc ),
157 0 : };
158 :
159 : /* This check is needed as a quirk of the fuzzer. If a sysvar account
160 : exists in the accounts database, but doesn't have any lamports,
161 : this means that the account does not exist. This wouldn't happen
162 : in a real execution environment. */
163 0 : if( FD_UNLIKELY( acc->vt->get_lamports( acc ) == 0UL ) ) {
164 0 : return NULL;
165 0 : }
166 :
167 0 : ulong total_sz = 0;
168 0 : err = fd_recent_block_hashes_decode_footprint( &ctx, &total_sz );
169 0 : if( FD_UNLIKELY( err ) ) {
170 0 : return NULL;
171 0 : }
172 :
173 0 : uchar * mem = fd_spad_alloc( spad, fd_recent_block_hashes_align(), total_sz );
174 0 : if( FD_UNLIKELY( !mem ) ) {
175 0 : FD_LOG_CRIT(( "fd_spad_alloc failed" ));
176 0 : }
177 :
178 : /* This would never happen in a real cluster, this is a workaround
179 : for fuzz-generated cases where sysvar accounts are not funded. */
180 0 : if( FD_UNLIKELY( acc->vt->get_lamports( acc ) == 0 ) ) {
181 0 : return NULL;
182 0 : }
183 :
184 0 : return fd_recent_block_hashes_decode_global( mem, &ctx );
185 0 : }
|