Line data Source code
1 : #include "fd_genesis_create.h"
2 :
3 : #include "../runtime/fd_system_ids.h"
4 : #include "../stakes/fd_stakes.h"
5 : #include "../runtime/program/fd_vote_program.h"
6 : #include "../runtime/program/vote/fd_vote_codec.h"
7 : #include "../runtime/sysvar/fd_sysvar_rent.h"
8 :
9 : /* TODO: Unify type with the one in fd_genesis_parse.c */
10 :
11 : struct fd_rust_duration {
12 : ulong seconds;
13 : uint nanoseconds;
14 : };
15 : typedef struct fd_rust_duration fd_rust_duration_t;
16 :
17 : struct fd_poh_config {
18 : fd_rust_duration_t target_tick_duration;
19 : ulong target_tick_count;
20 : uchar has_target_tick_count;
21 : ulong hashes_per_tick;
22 : uchar has_hashes_per_tick;
23 : };
24 : typedef struct fd_poh_config fd_poh_config_t;
25 :
26 : struct fd_genesis_account {
27 : ulong lamports;
28 : ulong data_len;
29 : uchar * data;
30 : fd_pubkey_t owner;
31 : uchar executable;
32 : ulong rent_epoch;
33 : };
34 : typedef struct fd_genesis_account fd_genesis_account_t;
35 :
36 : struct fd_genesis_account_pair {
37 : fd_pubkey_t key;
38 : fd_genesis_account_t account;
39 : };
40 : typedef struct fd_genesis_account_pair fd_genesis_account_pair_t;
41 :
42 : #define SORT_NAME sort_acct
43 1014 : #define SORT_KEY_T fd_genesis_account_pair_t
44 825 : #define SORT_BEFORE(a,b) (0>memcmp( (a).key.ul, (b).key.ul, sizeof(fd_pubkey_t) ))
45 : #include "../../util/tmpl/fd_sort.c"
46 :
47 : static inline uchar *
48 252 : emit_u8( uchar * p, uchar * end, uchar v ) {
49 252 : if( FD_UNLIKELY( p+1>end ) ) return NULL;
50 252 : *p = v;
51 252 : return p+1;
52 252 : }
53 :
54 : static inline uchar *
55 24 : emit_u32( uchar * p, uchar * end, uint v ) {
56 24 : if( FD_UNLIKELY( p+4>end ) ) return NULL;
57 24 : FD_STORE( uint, p, v );
58 24 : return p+4;
59 24 : }
60 :
61 : static inline uchar *
62 783 : emit_u64( uchar * p, uchar * end, ulong v ) {
63 783 : if( FD_UNLIKELY( p+8>end ) ) return NULL;
64 780 : FD_STORE( ulong, p, v );
65 780 : return p+8;
66 783 : }
67 :
68 : static inline uchar *
69 84 : emit_f64( uchar * p, uchar * end, double v ) {
70 84 : if( FD_UNLIKELY( p+8>end ) ) return NULL;
71 84 : FD_STORE( double, p, v );
72 84 : return p+8;
73 84 : }
74 :
75 : static inline uchar *
76 408 : emit_bytes( uchar * p, uchar * end, void const * src, ulong n ) {
77 408 : if( FD_UNLIKELY( p+n>end ) ) return NULL;
78 408 : fd_memcpy( p, src, n );
79 408 : return p+n;
80 408 : }
81 :
82 : /* Private struct mirroring the Solana GenesisConfig bincode layout.
83 : Only used locally for building up state before serialization. */
84 :
85 : struct genesis_solana {
86 : ulong creation_time;
87 : ulong accounts_len;
88 : fd_genesis_account_pair_t * accounts;
89 : ulong native_instruction_processors_len;
90 : ulong rewards_pools_len;
91 : ulong ticks_per_slot;
92 : ulong unused;
93 : fd_poh_config_t poh_config;
94 : ulong __backwards_compat_with_v0_23;
95 : fd_fee_rate_governor_t fee_rate_governor;
96 : fd_rent_t rent;
97 : fd_inflation_t inflation;
98 : fd_epoch_schedule_t epoch_schedule;
99 : uint cluster_type;
100 : };
101 : typedef struct genesis_solana genesis_solana_t;
102 :
103 : /* genesis_encode serializes a genesis_solana_t into a bincode blob
104 : byte-for-byte compatible with Anza's genesis.bin format. Returns the
105 : number of bytes written, or 0 on failure (buffer too small). */
106 :
107 : static ulong
108 : genesis_encode( genesis_solana_t const * g,
109 : uchar * buf,
110 15 : ulong bufsz ) {
111 15 : uchar * p = buf;
112 15 : uchar * end = buf + bufsz;
113 :
114 1551 : # define EMIT(expr) do { p = (expr); if( FD_UNLIKELY( !p ) ) return 0UL; } while(0)
115 :
116 15 : EMIT( emit_u64( p, end, g->creation_time ) );
117 :
118 : /* accounts vector */
119 12 : EMIT( emit_u64( p, end, g->accounts_len ) );
120 204 : for( ulong i=0; i<g->accounts_len; i++ ) {
121 192 : fd_genesis_account_pair_t const * a = &g->accounts[i];
122 192 : EMIT( emit_bytes( p, end, a->key.key, 32 ) );
123 192 : EMIT( emit_u64( p, end, a->account.lamports ) );
124 192 : EMIT( emit_u64( p, end, a->account.data_len ) );
125 192 : if( a->account.data_len )
126 24 : EMIT( emit_bytes( p, end, a->account.data, a->account.data_len ) );
127 192 : EMIT( emit_bytes( p, end, a->account.owner.key, 32 ) );
128 192 : EMIT( emit_u8( p, end, !!a->account.executable ) );
129 192 : EMIT( emit_u64( p, end, a->account.rent_epoch ) );
130 192 : }
131 :
132 : /* native_instruction_processors vector
133 : TODO: currently always empty */
134 12 : EMIT( emit_u64( p, end, g->native_instruction_processors_len ) );
135 :
136 : /* rewards_pools vector
137 : TODO: currently always empty */
138 12 : EMIT( emit_u64( p, end, g->rewards_pools_len ) );
139 :
140 12 : EMIT( emit_u64( p, end, g->ticks_per_slot ) );
141 12 : EMIT( emit_u64( p, end, g->unused ) );
142 :
143 : /* poh_config
144 : TODO: has target tick count always == 0 */
145 12 : EMIT( emit_u64( p, end, g->poh_config.target_tick_duration.seconds ) );
146 12 : EMIT( emit_u32( p, end, g->poh_config.target_tick_duration.nanoseconds ) );
147 12 : EMIT( emit_u8( p, end, !!g->poh_config.has_target_tick_count ) );
148 12 : if( g->poh_config.has_target_tick_count )
149 0 : EMIT( emit_u64( p, end, g->poh_config.target_tick_count ) );
150 12 : EMIT( emit_u8( p, end, !!g->poh_config.has_hashes_per_tick ) );
151 12 : if( g->poh_config.has_hashes_per_tick )
152 0 : EMIT( emit_u64( p, end, g->poh_config.hashes_per_tick ) );
153 :
154 : /* TODO: always set to 0 */
155 12 : EMIT( emit_u64( p, end, g->__backwards_compat_with_v0_23 ) );
156 :
157 : /* fee_rate_governor */
158 12 : EMIT( emit_u64( p, end, g->fee_rate_governor.target_lamports_per_signature ) );
159 12 : EMIT( emit_u64( p, end, g->fee_rate_governor.target_signatures_per_slot ) );
160 12 : EMIT( emit_u64( p, end, g->fee_rate_governor.min_lamports_per_signature ) );
161 12 : EMIT( emit_u64( p, end, g->fee_rate_governor.max_lamports_per_signature ) );
162 12 : EMIT( emit_u8( p, end, g->fee_rate_governor.burn_percent ) );
163 :
164 : /* rent */
165 12 : EMIT( emit_u64( p, end, g->rent.lamports_per_uint8_year ) );
166 12 : EMIT( emit_f64( p, end, g->rent.exemption_threshold ) );
167 12 : EMIT( emit_u8( p, end, g->rent.burn_percent ) );
168 :
169 : /* inflation */
170 12 : EMIT( emit_f64( p, end, g->inflation.initial ) );
171 12 : EMIT( emit_f64( p, end, g->inflation.terminal ) );
172 12 : EMIT( emit_f64( p, end, g->inflation.taper ) );
173 12 : EMIT( emit_f64( p, end, g->inflation.foundation ) );
174 12 : EMIT( emit_f64( p, end, g->inflation.foundation_term ) );
175 12 : EMIT( emit_f64( p, end, g->inflation.unused ) );
176 :
177 : /* epoch_schedule */
178 12 : EMIT( emit_u64( p, end, g->epoch_schedule.slots_per_epoch ) );
179 12 : EMIT( emit_u64( p, end, g->epoch_schedule.leader_schedule_slot_offset ) );
180 12 : EMIT( emit_u8( p, end, !!g->epoch_schedule.warmup ) );
181 12 : EMIT( emit_u64( p, end, g->epoch_schedule.first_normal_epoch ) );
182 12 : EMIT( emit_u64( p, end, g->epoch_schedule.first_normal_slot ) );
183 :
184 12 : EMIT( emit_u32( p, end, g->cluster_type ) );
185 :
186 12 : # undef EMIT
187 12 : return (ulong)(p - buf);
188 12 : }
189 :
190 : static ulong
191 : genesis_create( void * buf,
192 : ulong bufsz,
193 15 : fd_genesis_options_t const * options ) {
194 :
195 15 : # define REQUIRE(c) \
196 45 : do { \
197 45 : if( FD_UNLIKELY( !(c) ) ) { \
198 0 : FD_LOG_WARNING(( "FAIL: %s", #c )); \
199 0 : return 0UL; \
200 0 : } \
201 45 : } while(0);
202 :
203 15 : genesis_solana_t genesis[1] = {0};
204 :
205 15 : genesis->cluster_type = 3; /* development */
206 :
207 15 : genesis->creation_time = options->creation_time;
208 15 : genesis->ticks_per_slot = options->ticks_per_slot;
209 15 : REQUIRE( genesis->ticks_per_slot );
210 :
211 15 : genesis->unused = 1024UL; /* match Anza genesis byte-for-byte */
212 :
213 15 : genesis->poh_config.has_hashes_per_tick = !!options->hashes_per_tick;
214 15 : genesis->poh_config.hashes_per_tick = options->hashes_per_tick;
215 :
216 15 : ulong target_tick_micros = options->target_tick_duration_micros;
217 15 : REQUIRE( target_tick_micros );
218 15 : genesis->poh_config.target_tick_duration = (fd_rust_duration_t) {
219 15 : .seconds = target_tick_micros / 1000000UL,
220 15 : .nanoseconds = (uint)( target_tick_micros % 1000000UL * 1000UL ),
221 15 : };
222 :
223 : /* Create fee rate governor */
224 :
225 15 : genesis->fee_rate_governor = (fd_fee_rate_governor_t) {
226 15 : .target_lamports_per_signature = 10000UL,
227 15 : .target_signatures_per_slot = 20000UL,
228 15 : .min_lamports_per_signature = 5000UL,
229 15 : .max_lamports_per_signature = 100000UL,
230 15 : .burn_percent = 50,
231 15 : };
232 :
233 : /* Create rent configuration */
234 :
235 15 : genesis->rent = (fd_rent_t) {
236 15 : .lamports_per_uint8_year = 3480,
237 15 : .exemption_threshold = 2.0,
238 15 : .burn_percent = 50,
239 15 : };
240 :
241 : /* Create inflation configuration */
242 :
243 15 : genesis->inflation = (fd_inflation_t) {
244 15 : .initial = 0.08,
245 15 : .terminal = 0.015,
246 15 : .taper = 0.15,
247 15 : .foundation = 0.05,
248 15 : .foundation_term = 7.0,
249 15 : };
250 :
251 : /* Create epoch schedule */
252 : /* TODO The epoch schedule should be configurable! */
253 :
254 : /* If warmup is enabled:
255 : MINIMUM_SLOTS_PER_EPOCH = 32
256 : first_normal_epoch = log2( slots_per_epoch ) - log2( MINIMUM_SLOTS_PER_EPOCH )
257 : first_normal_slot = MINIMUM_SLOTS_PER_EPOCH * ( 2^( first_normal_epoch ) - 1 )
258 : */
259 :
260 15 : genesis->epoch_schedule = (fd_epoch_schedule_t) {
261 15 : .slots_per_epoch = 8192UL,
262 15 : .leader_schedule_slot_offset = 8192UL,
263 15 : .warmup = fd_uchar_if( options->warmup_epochs, 1, 0 ),
264 15 : .first_normal_epoch = fd_ulong_if( options->warmup_epochs, 8UL, 0UL ),
265 15 : .first_normal_slot = fd_ulong_if( options->warmup_epochs, 8160UL, 0UL ),
266 15 : };
267 :
268 : /* Create faucet account */
269 :
270 15 : fd_genesis_account_pair_t const faucet_account = {
271 15 : .key = options->faucet_pubkey,
272 15 : .account = {
273 15 : .lamports = options->faucet_balance,
274 15 : .owner = fd_solana_system_program_id
275 15 : }
276 15 : };
277 15 : ulong const faucet_account_index = genesis->accounts_len++;
278 :
279 : /* Create identity account (vote authority, withdraw authority) */
280 :
281 15 : fd_genesis_account_pair_t const identity_account = {
282 15 : .key = options->identity_pubkey,
283 15 : .account = {
284 15 : .lamports = 500000000000UL /* 500 SOL */,
285 15 : .owner = fd_solana_system_program_id
286 15 : }
287 15 : };
288 15 : ulong const identity_account_index = genesis->accounts_len++;
289 :
290 : /* Create vote account */
291 :
292 15 : ulong const vote_account_index = genesis->accounts_len++;
293 :
294 15 : uchar vote_state_data[ FD_VOTE_STATE_V3_SZ ] = {0};
295 :
296 15 : FD_SCRATCH_SCOPE_BEGIN {
297 15 : fd_vote_state_versioned_t versioned[1];
298 15 : fd_vote_state_versioned_new( versioned, fd_vote_state_versioned_enum_v3 );
299 :
300 15 : fd_vote_state_v3_t * vote_state = &versioned->v3;
301 15 : vote_state->node_pubkey = options->identity_pubkey;
302 15 : vote_state->authorized_withdrawer = options->identity_pubkey;
303 15 : vote_state->commission = 100;
304 :
305 15 : fd_vote_authorized_voter_t * voter = fd_vote_authorized_voters_pool_ele_acquire( vote_state->authorized_voters.pool );
306 15 : *voter = (fd_vote_authorized_voter_t) {
307 15 : .epoch = 0UL,
308 15 : .pubkey = options->identity_pubkey,
309 15 : .prio = options->identity_pubkey.uc[0],
310 15 : };
311 15 : fd_vote_authorized_voters_treap_ele_insert( vote_state->authorized_voters.treap, voter, vote_state->authorized_voters.pool );
312 :
313 15 : REQUIRE( !fd_vote_state_versioned_serialize( versioned, vote_state_data, sizeof(vote_state_data) ) );
314 15 : }
315 15 : FD_SCRATCH_SCOPE_END;
316 :
317 : /* Create stake account */
318 :
319 15 : ulong const stake_account_index = genesis->accounts_len++;
320 :
321 15 : uchar stake_data[ FD_STAKE_STATE_SZ ] = {0};
322 :
323 15 : ulong stake_state_min_bal = fd_rent_exempt_minimum_balance( &genesis->rent, FD_STAKE_STATE_SZ );
324 15 : ulong vote_min_bal = fd_rent_exempt_minimum_balance( &genesis->rent, FD_VOTE_STATE_V3_SZ );
325 :
326 15 : do {
327 15 : FD_STORE( fd_stake_state_t, stake_data, ((fd_stake_state_t) {
328 15 : .stake_type = FD_STAKE_STATE_STAKE,
329 15 : .stake = {
330 15 : .meta = {
331 15 : .rent_exempt_reserve = stake_state_min_bal,
332 15 : .staker = options->identity_pubkey,
333 15 : .withdrawer = options->identity_pubkey,
334 15 : },
335 15 : .stake = (fd_stake_t) {
336 15 : .delegation = (fd_delegation_t) {
337 15 : .voter_pubkey = options->vote_pubkey,
338 15 : .stake = fd_ulong_max( stake_state_min_bal, options->vote_account_stake ),
339 15 : .activation_epoch = ULONG_MAX, /* bootstrap stake denoted with ULONG_MAX */
340 15 : .deactivation_epoch = ULONG_MAX,
341 15 : .warmup_cooldown_rate = 0.25
342 15 : },
343 15 : .credits_observed = 0UL
344 15 : }
345 15 : }
346 15 : }) );
347 15 : } while(0);
348 :
349 : /* Read enabled features */
350 :
351 15 : ulong feature_cnt = 0UL;
352 15 : fd_pubkey_t * features =
353 15 : fd_scratch_alloc( alignof(fd_pubkey_t), FD_FEATURE_ID_CNT * sizeof(fd_pubkey_t) );
354 :
355 15 : if( options->features ) {
356 6 : for( fd_feature_id_t const * id = fd_feature_iter_init();
357 1668 : !fd_feature_iter_done( id );
358 1662 : id = fd_feature_iter_next( id ) ) {
359 1662 : if( fd_features_get( options->features, id ) == 0UL )
360 0 : features[ feature_cnt++ ] = id->id;
361 1662 : }
362 6 : }
363 :
364 : /* Allocate the account table */
365 :
366 15 : ulong default_funded_cnt = options->fund_initial_accounts;
367 :
368 15 : ulong default_funded_idx = genesis->accounts_len; genesis->accounts_len += default_funded_cnt;
369 15 : ulong feature_gate_idx = genesis->accounts_len; genesis->accounts_len += feature_cnt;
370 :
371 15 : genesis->accounts = fd_scratch_alloc( alignof(fd_genesis_account_pair_t),
372 15 : genesis->accounts_len * sizeof(fd_genesis_account_pair_t) );
373 15 : fd_memset( genesis->accounts, 0, genesis->accounts_len * sizeof(fd_genesis_account_pair_t) );
374 :
375 15 : genesis->accounts[ faucet_account_index ] = faucet_account;
376 15 : genesis->accounts[ identity_account_index ] = identity_account;
377 15 : genesis->accounts[ stake_account_index ] = (fd_genesis_account_pair_t) {
378 15 : .key = options->stake_pubkey,
379 15 : .account = (fd_genesis_account_t) {
380 15 : .lamports = fd_ulong_max( stake_state_min_bal, options->vote_account_stake ),
381 15 : .data_len = FD_STAKE_STATE_SZ,
382 15 : .data = stake_data,
383 15 : .owner = fd_solana_stake_program_id
384 15 : }
385 15 : };
386 15 : genesis->accounts[ vote_account_index ] = (fd_genesis_account_pair_t) {
387 15 : .key = options->vote_pubkey,
388 15 : .account = (fd_genesis_account_t) {
389 15 : .lamports = vote_min_bal,
390 15 : .data_len = FD_VOTE_STATE_V3_SZ,
391 15 : .data = vote_state_data,
392 15 : .owner = fd_solana_vote_program_id
393 15 : }
394 15 : };
395 :
396 : /* Set up primordial accounts */
397 :
398 15 : ulong default_funded_balance = options->fund_initial_amount_lamports;
399 159 : for( ulong j=0UL; j<default_funded_cnt; j++ ) {
400 144 : fd_genesis_account_pair_t * pair = &genesis->accounts[ default_funded_idx+j ];
401 :
402 144 : uchar privkey[ 32 ] = {0};
403 144 : FD_STORE( ulong, privkey, j );
404 144 : fd_sha512_t sha[1];
405 144 : fd_ed25519_public_from_private( pair->key.key, privkey, sha );
406 :
407 144 : pair->account = (fd_genesis_account_t) {
408 144 : .lamports = default_funded_balance,
409 144 : .data_len = 0UL,
410 144 : .owner = fd_solana_system_program_id
411 144 : };
412 144 : }
413 :
414 15 : #define FEATURE_ENABLED_SZ 9UL
415 15 : static const uchar feature_enabled_data[ FEATURE_ENABLED_SZ ] = { 1, 0, 0, 0, 0, 0, 0, 0, 0 };
416 15 : ulong default_feature_enabled_balance = fd_rent_exempt_minimum_balance( &genesis->rent, FEATURE_ENABLED_SZ );
417 :
418 : /* Set up feature gate accounts */
419 15 : for( ulong j=0UL; j<feature_cnt; j++ ) {
420 0 : fd_genesis_account_pair_t * pair = &genesis->accounts[ feature_gate_idx+j ];
421 :
422 0 : pair->key = features[ j ];
423 0 : pair->account = (fd_genesis_account_t) {
424 0 : .lamports = default_feature_enabled_balance,
425 0 : .data_len = FEATURE_ENABLED_SZ,
426 0 : .data = (uchar *)feature_enabled_data,
427 0 : .owner = fd_solana_feature_program_id
428 0 : };
429 0 : }
430 15 : #undef FEATURE_ENABLED_SZ
431 :
432 : /* Sort and check for duplicates */
433 :
434 15 : sort_acct_inplace( genesis->accounts, genesis->accounts_len );
435 :
436 204 : for( ulong j=1UL; j < genesis->accounts_len; j++ ) {
437 189 : if( 0==memcmp( genesis->accounts[j-1].key.ul, genesis->accounts[j].key.ul, sizeof(fd_pubkey_t) ) ) {
438 0 : char dup_cstr[ FD_BASE58_ENCODED_32_SZ ];
439 0 : fd_base58_encode_32( genesis->accounts[j].key.uc, NULL, dup_cstr );
440 0 : FD_LOG_WARNING(( "Account %s is duplicate", dup_cstr ));
441 0 : return 0UL;
442 0 : }
443 189 : }
444 :
445 : /* Serialize bincode blob */
446 :
447 15 : ulong encoded_sz = genesis_encode( genesis, (uchar *)buf, bufsz );
448 15 : if( FD_UNLIKELY( !encoded_sz ) ) {
449 3 : FD_LOG_WARNING(( "Failed to encode genesis blob (bufsz=%lu)", bufsz ));
450 3 : return 0UL;
451 3 : }
452 12 : return encoded_sz;
453 :
454 15 : # undef REQUIRE
455 15 : }
456 :
457 : ulong
458 : fd_genesis_create( void * buf,
459 : ulong bufsz,
460 15 : fd_genesis_options_t const * options ) {
461 15 : fd_scratch_push();
462 15 : ulong ret = genesis_create( buf, bufsz, options );
463 15 : fd_scratch_pop();
464 15 : return ret;
465 15 : }
|