Line data Source code
1 : #include "fd_genesis_create.h"
2 :
3 : #include "../runtime/fd_system_ids.h"
4 : #include "../runtime/fd_pubkey_utils.h"
5 : #include "../stakes/fd_stakes.h"
6 : #include "../runtime/program/fd_vote_program.h"
7 : #include "../runtime/program/fd_bpf_loader_program.h"
8 : #include "../runtime/program/vote/fd_vote_codec_tmpl.h"
9 : #include "../runtime/sysvar/fd_sysvar_rent.h"
10 : #include "../../ballet/sha256/fd_sha256.h"
11 :
12 : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/genesis/src/main.rs#L69 */
13 30 : #define FD_GENESIS_VAT_MINIMUM_LAMPORTS (1600000000UL*100UL)
14 :
15 : /* TODO: Unify type with the one in fd_genesis_parse.c */
16 :
17 : struct fd_rust_duration {
18 : ulong seconds;
19 : uint nanoseconds;
20 : };
21 : typedef struct fd_rust_duration fd_rust_duration_t;
22 :
23 : struct fd_poh_config {
24 : fd_rust_duration_t target_tick_duration;
25 : ulong target_tick_count;
26 : uchar has_target_tick_count;
27 : ulong hashes_per_tick;
28 : uchar has_hashes_per_tick;
29 : };
30 : typedef struct fd_poh_config fd_poh_config_t;
31 :
32 : struct fd_genesis_account {
33 : ulong lamports;
34 : ulong data_len;
35 : uchar * data;
36 : fd_pubkey_t owner;
37 : uchar executable;
38 : ulong rent_epoch;
39 : };
40 : typedef struct fd_genesis_account fd_genesis_account_t;
41 :
42 : struct fd_genesis_account_pair {
43 : fd_pubkey_t key;
44 : fd_genesis_account_t account;
45 : };
46 : typedef struct fd_genesis_account_pair fd_genesis_account_pair_t;
47 :
48 : #define SORT_NAME sort_acct
49 2181 : #define SORT_KEY_T fd_genesis_account_pair_t
50 2124 : #define SORT_BEFORE(a,b) (0>memcmp( (a).key.ul, (b).key.ul, sizeof(fd_pubkey_t) ))
51 : #include "../../util/tmpl/fd_sort.c"
52 :
53 : /* SPL Token state layout sizes. See
54 : https://github.com/solana-program/token/blob/program%40v8.0.0/interface/src/state.rs */
55 :
56 12 : #define SPL_TOKEN_MINT_SZ ( 82UL)
57 195 : #define SPL_TOKEN_ACCOUNT_SZ (165UL)
58 :
59 : /* token_addr derives the address of one of the token accounts, or of
60 : the mint. The addresses are domain separated SHA-256 hashes, so they
61 : cannot collide with the ed25519 keys of the primordial accounts. */
62 :
63 6 : #define TOKEN_ADDR_KIND_MINT ((uchar)0xFF)
64 :
65 : FD_STATIC_ASSERT( FD_GENESIS_TOKEN_ACCOUNTS_PER_ACCOUNT<TOKEN_ADDR_KIND_MINT, token_addr_kind );
66 :
67 : static void
68 : token_addr( fd_pubkey_t * out,
69 : ulong acct_idx,
70 198 : uchar kind ) {
71 198 : fd_sha256_t sha[1];
72 198 : fd_sha256_init( sha );
73 198 : fd_sha256_append( sha, "fd_genesis_token", 16UL );
74 198 : fd_sha256_append( sha, &kind, sizeof(uchar) );
75 198 : fd_sha256_append( sha, &acct_idx, sizeof(ulong) );
76 198 : fd_sha256_fini( sha, out->uc );
77 198 : }
78 :
79 : void
80 6 : fd_genesis_token_mint_address( fd_pubkey_t * out ) {
81 6 : token_addr( out, 0UL, TOKEN_ADDR_KIND_MINT );
82 6 : }
83 :
84 : void
85 : fd_genesis_token_account_address( fd_pubkey_t * out,
86 : ulong acct_idx,
87 192 : ulong token_idx ) {
88 192 : token_addr( out, acct_idx, (uchar)token_idx );
89 192 : }
90 :
91 : static inline uchar *
92 432 : emit_u8( uchar * p, uchar * end, uchar v ) {
93 432 : if( FD_UNLIKELY( (ulong)end-(ulong)p<1UL ) ) return NULL;
94 432 : *p = v;
95 432 : return p+1;
96 432 : }
97 :
98 : static inline uchar *
99 30 : emit_u32( uchar * p, uchar * end, uint v ) {
100 30 : if( FD_UNLIKELY( (ulong)end-(ulong)p<4UL ) ) return NULL;
101 30 : FD_STORE( uint, p, v );
102 30 : return p+4;
103 30 : }
104 :
105 : static inline uchar *
106 1329 : emit_u64( uchar * p, uchar * end, ulong v ) {
107 1329 : if( FD_UNLIKELY( (ulong)end-(ulong)p<8UL ) ) return NULL;
108 1326 : FD_STORE( ulong, p, v );
109 1326 : return p+8;
110 1329 : }
111 :
112 : static inline uchar *
113 105 : emit_f64( uchar * p, uchar * end, double v ) {
114 105 : if( FD_UNLIKELY( (ulong)end-(ulong)p<8UL ) ) return NULL;
115 105 : FD_STORE( double, p, v );
116 105 : return p+8;
117 105 : }
118 :
119 : static inline uchar *
120 849 : emit_bytes( uchar * p, uchar * end, void const * src, ulong n ) {
121 849 : if( FD_UNLIKELY( (ulong)end-(ulong)p<n ) ) return NULL;
122 849 : if( FD_LIKELY( n ) ) fd_memcpy( p, src, n );
123 849 : return p+n;
124 849 : }
125 :
126 : /* Private struct mirroring the Solana GenesisConfig bincode layout.
127 : Only used locally for building up state before serialization. */
128 :
129 : struct genesis_solana {
130 : ulong creation_time;
131 : ulong accounts_len;
132 : fd_genesis_account_pair_t * accounts;
133 : ulong native_instruction_processors_len;
134 : ulong rewards_pools_len;
135 : ulong ticks_per_slot;
136 : ulong unused;
137 : fd_poh_config_t poh_config;
138 : ulong __backwards_compat_with_v0_23;
139 : fd_fee_rate_governor_t fee_rate_governor;
140 : fd_rent_t rent;
141 : fd_inflation_t inflation;
142 : fd_epoch_schedule_t epoch_schedule;
143 : uint cluster_type;
144 : };
145 : typedef struct genesis_solana genesis_solana_t;
146 :
147 : /* genesis_encode serializes a genesis_solana_t into a bincode blob
148 : byte-for-byte compatible with Anza's genesis.bin format. Returns the
149 : number of bytes written, or 0 on failure (buffer too small). */
150 :
151 : static ulong
152 : genesis_encode( genesis_solana_t const * g,
153 : uchar * buf,
154 21 : ulong bufsz ) {
155 21 : if( FD_UNLIKELY( !buf ) ) return 0UL;
156 :
157 18 : uchar * p = buf;
158 18 : uchar * end = buf + bufsz;
159 :
160 2745 : # define EMIT(expr) do { p = (expr); if( FD_UNLIKELY( !p ) ) return 0UL; } while(0)
161 :
162 18 : EMIT( emit_u64( p, end, g->creation_time ) );
163 :
164 : /* accounts vector */
165 15 : EMIT( emit_u64( p, end, g->accounts_len ) );
166 372 : for( ulong i=0; i<g->accounts_len; i++ ) {
167 357 : fd_genesis_account_pair_t const * a = &g->accounts[i];
168 357 : EMIT( emit_bytes( p, end, a->key.key, 32 ) );
169 357 : EMIT( emit_u64( p, end, a->account.lamports ) );
170 357 : EMIT( emit_u64( p, end, a->account.data_len ) );
171 357 : if( a->account.data_len )
172 135 : EMIT( emit_bytes( p, end, a->account.data, a->account.data_len ) );
173 357 : EMIT( emit_bytes( p, end, a->account.owner.key, 32 ) );
174 357 : EMIT( emit_u8( p, end, !!a->account.executable ) );
175 357 : EMIT( emit_u64( p, end, a->account.rent_epoch ) );
176 357 : }
177 :
178 : /* native_instruction_processors vector
179 : TODO: currently always empty */
180 15 : EMIT( emit_u64( p, end, g->native_instruction_processors_len ) );
181 :
182 : /* rewards_pools vector
183 : TODO: currently always empty */
184 15 : EMIT( emit_u64( p, end, g->rewards_pools_len ) );
185 :
186 15 : EMIT( emit_u64( p, end, g->ticks_per_slot ) );
187 15 : EMIT( emit_u64( p, end, g->unused ) );
188 :
189 : /* poh_config
190 : TODO: has target tick count always == 0 */
191 15 : EMIT( emit_u64( p, end, g->poh_config.target_tick_duration.seconds ) );
192 15 : EMIT( emit_u32( p, end, g->poh_config.target_tick_duration.nanoseconds ) );
193 15 : EMIT( emit_u8( p, end, !!g->poh_config.has_target_tick_count ) );
194 15 : if( g->poh_config.has_target_tick_count )
195 0 : EMIT( emit_u64( p, end, g->poh_config.target_tick_count ) );
196 15 : EMIT( emit_u8( p, end, !!g->poh_config.has_hashes_per_tick ) );
197 15 : if( g->poh_config.has_hashes_per_tick )
198 0 : EMIT( emit_u64( p, end, g->poh_config.hashes_per_tick ) );
199 :
200 : /* TODO: always set to 0 */
201 15 : EMIT( emit_u64( p, end, g->__backwards_compat_with_v0_23 ) );
202 :
203 : /* fee_rate_governor */
204 15 : EMIT( emit_u64( p, end, g->fee_rate_governor.target_lamports_per_signature ) );
205 15 : EMIT( emit_u64( p, end, g->fee_rate_governor.target_signatures_per_slot ) );
206 15 : EMIT( emit_u64( p, end, g->fee_rate_governor.min_lamports_per_signature ) );
207 15 : EMIT( emit_u64( p, end, g->fee_rate_governor.max_lamports_per_signature ) );
208 15 : EMIT( emit_u8( p, end, g->fee_rate_governor.burn_percent ) );
209 :
210 : /* rent */
211 15 : EMIT( emit_u64( p, end, g->rent.lamports_per_uint8_year ) );
212 15 : EMIT( emit_f64( p, end, g->rent.exemption_threshold ) );
213 15 : EMIT( emit_u8( p, end, g->rent.burn_percent ) );
214 :
215 : /* inflation */
216 15 : EMIT( emit_f64( p, end, g->inflation.initial ) );
217 15 : EMIT( emit_f64( p, end, g->inflation.terminal ) );
218 15 : EMIT( emit_f64( p, end, g->inflation.taper ) );
219 15 : EMIT( emit_f64( p, end, g->inflation.foundation ) );
220 15 : EMIT( emit_f64( p, end, g->inflation.foundation_term ) );
221 15 : EMIT( emit_f64( p, end, g->inflation.unused ) );
222 :
223 : /* epoch_schedule */
224 15 : EMIT( emit_u64( p, end, g->epoch_schedule.slots_per_epoch ) );
225 15 : EMIT( emit_u64( p, end, g->epoch_schedule.leader_schedule_slot_offset ) );
226 15 : EMIT( emit_u8( p, end, !!g->epoch_schedule.warmup ) );
227 15 : EMIT( emit_u64( p, end, g->epoch_schedule.first_normal_epoch ) );
228 15 : EMIT( emit_u64( p, end, g->epoch_schedule.first_normal_slot ) );
229 :
230 15 : EMIT( emit_u32( p, end, g->cluster_type ) );
231 :
232 15 : # undef EMIT
233 15 : return (ulong)(p - buf);
234 15 : }
235 :
236 : static ulong
237 : genesis_create( void * buf,
238 : ulong bufsz,
239 30 : fd_genesis_options_t const * options ) {
240 :
241 30 : # define REQUIRE(c) \
242 291 : do { \
243 291 : if( FD_UNLIKELY( !(c) ) ) { \
244 9 : FD_LOG_WARNING(( "FAIL: %s", #c )); \
245 9 : return 0UL; \
246 9 : } \
247 291 : } while(0);
248 :
249 30 : genesis_solana_t genesis[1] = {0};
250 :
251 30 : genesis->cluster_type = 3; /* development */
252 :
253 30 : genesis->creation_time = options->creation_time;
254 30 : genesis->ticks_per_slot = options->ticks_per_slot;
255 30 : REQUIRE( genesis->ticks_per_slot );
256 :
257 30 : genesis->unused = 1024UL; /* match Anza genesis byte-for-byte */
258 :
259 30 : genesis->poh_config.has_hashes_per_tick = !!options->hashes_per_tick;
260 30 : genesis->poh_config.hashes_per_tick = options->hashes_per_tick;
261 :
262 30 : ulong target_tick_micros = options->target_tick_duration_micros;
263 30 : REQUIRE( target_tick_micros );
264 30 : genesis->poh_config.target_tick_duration = (fd_rust_duration_t) {
265 30 : .seconds = target_tick_micros / 1000000UL,
266 30 : .nanoseconds = (uint)( target_tick_micros % 1000000UL * 1000UL ),
267 30 : };
268 :
269 : /* Create fee rate governor */
270 :
271 30 : genesis->fee_rate_governor = (fd_fee_rate_governor_t) {
272 30 : .target_lamports_per_signature = 10000UL,
273 30 : .target_signatures_per_slot = 20000UL,
274 30 : .min_lamports_per_signature = 5000UL,
275 30 : .max_lamports_per_signature = 100000UL,
276 30 : .burn_percent = 50,
277 30 : };
278 :
279 : /* Create rent configuration */
280 :
281 30 : genesis->rent = (fd_rent_t) {
282 30 : .lamports_per_uint8_year = 3480,
283 30 : .exemption_threshold = 2.0,
284 30 : .burn_percent = 50,
285 30 : };
286 :
287 : /* Create inflation configuration */
288 :
289 30 : genesis->inflation = (fd_inflation_t) {
290 30 : .initial = 0.08,
291 30 : .terminal = 0.015,
292 30 : .taper = 0.15,
293 30 : .foundation = 0.05,
294 30 : .foundation_term = 7.0,
295 30 : };
296 :
297 : /* Create epoch schedule */
298 : /* TODO The epoch schedule should be configurable! */
299 :
300 : /* If warmup is enabled:
301 : MINIMUM_SLOTS_PER_EPOCH = 32
302 : first_normal_epoch = log2( slots_per_epoch ) - log2( MINIMUM_SLOTS_PER_EPOCH )
303 : first_normal_slot = MINIMUM_SLOTS_PER_EPOCH * ( 2^( first_normal_epoch ) - 1 )
304 : */
305 :
306 30 : genesis->epoch_schedule = (fd_epoch_schedule_t) {
307 30 : .slots_per_epoch = 8192UL,
308 30 : .leader_schedule_slot_offset = 8192UL,
309 30 : .warmup = fd_uchar_if( options->warmup_epochs, 1, 0 ),
310 30 : .first_normal_epoch = fd_ulong_if( options->warmup_epochs, 8UL, 0UL ),
311 30 : .first_normal_slot = fd_ulong_if( options->warmup_epochs, 8160UL, 0UL ),
312 30 : };
313 :
314 : /* Create faucet account */
315 :
316 30 : fd_genesis_account_pair_t const faucet_account = {
317 30 : .key = options->faucet_pubkey,
318 30 : .account = {
319 30 : .lamports = options->faucet_balance,
320 30 : .owner = fd_solana_system_program_id
321 30 : }
322 30 : };
323 30 : ulong const faucet_account_index = genesis->accounts_len++;
324 :
325 : /* Create identity account (vote authority, withdraw authority) */
326 :
327 30 : fd_genesis_account_pair_t const identity_account = {
328 30 : .key = options->identity_pubkey,
329 30 : .account = {
330 30 : .lamports = 500000000000UL /* 500 SOL */,
331 30 : .owner = fd_solana_system_program_id
332 30 : }
333 30 : };
334 30 : ulong const identity_account_index = genesis->accounts_len++;
335 :
336 : /* Create vote account */
337 :
338 30 : ulong const vote_account_index = genesis->accounts_len++;
339 :
340 30 : uchar vote_state_data[ FD_VOTE_STATE_V4_SZ ] = {0};
341 :
342 30 : FD_SCRATCH_SCOPE_BEGIN {
343 30 : fd_vote_state_versioned_t versioned[1];
344 30 : fd_vote_state_versioned_new( versioned, fd_vote_state_versioned_enum_v4 );
345 :
346 30 : fd_vote_state_v4_t * vote_state = &versioned->v4;
347 30 : vote_state->node_pubkey = options->identity_pubkey;
348 30 : vote_state->authorized_withdrawer = options->identity_pubkey;
349 30 : vote_state->inflation_rewards_collector = options->vote_pubkey;
350 30 : vote_state->block_revenue_collector = options->identity_pubkey;
351 30 : vote_state->inflation_rewards_commission_bps = 10000;
352 30 : vote_state->block_revenue_commission_bps = 0;
353 30 : vote_state->has_bls_pubkey_compressed = 1;
354 :
355 30 : fd_vote_authorized_voter_t * voter = fd_vote_authorized_voters_pool_ele_acquire( vote_state->authorized_voters.pool );
356 30 : *voter = (fd_vote_authorized_voter_t) {
357 30 : .epoch = 0UL,
358 30 : .pubkey = options->identity_pubkey,
359 30 : .prio = options->identity_pubkey.uc[0],
360 30 : };
361 30 : fd_vote_authorized_voters_treap_ele_insert( vote_state->authorized_voters.treap, voter, vote_state->authorized_voters.pool );
362 :
363 30 : REQUIRE( !fd_vote_state_versioned_serialize( versioned, vote_state_data, sizeof(vote_state_data) ) );
364 30 : }
365 30 : FD_SCRATCH_SCOPE_END;
366 :
367 : /* Create stake account */
368 :
369 30 : ulong const stake_account_index = genesis->accounts_len++;
370 :
371 30 : uchar stake_data[ FD_STAKE_STATE_SZ ] = {0};
372 :
373 30 : ulong stake_state_min_bal = fd_rent_exempt_minimum_balance( &genesis->rent, FD_STAKE_STATE_SZ );
374 30 : ulong vote_min_bal = fd_rent_exempt_minimum_balance( &genesis->rent, FD_VOTE_STATE_V4_SZ ) +
375 30 : FD_GENESIS_VAT_MINIMUM_LAMPORTS;
376 :
377 30 : do {
378 30 : FD_STORE( fd_stake_state_t, stake_data, ((fd_stake_state_t) {
379 30 : .stake_type = FD_STAKE_STATE_STAKE,
380 30 : .stake = {
381 30 : .meta = {
382 30 : .rent_exempt_reserve = stake_state_min_bal,
383 30 : .staker = options->identity_pubkey,
384 30 : .withdrawer = options->identity_pubkey,
385 30 : },
386 30 : .stake = (fd_stake_t) {
387 30 : .delegation = (fd_delegation_t) {
388 30 : .voter_pubkey = options->vote_pubkey,
389 30 : .stake = fd_ulong_max( stake_state_min_bal, options->vote_account_stake ),
390 30 : .activation_epoch = ULONG_MAX, /* bootstrap stake denoted with ULONG_MAX */
391 30 : .deactivation_epoch = ULONG_MAX,
392 30 : .warmup_cooldown_rate = 0.25
393 30 : },
394 30 : .credits_observed = 0UL
395 30 : }
396 30 : }
397 30 : }) );
398 30 : } while(0);
399 :
400 : /* Read enabled features */
401 :
402 30 : ulong feature_cnt = 0UL;
403 30 : ulong const features_sz = FD_FEATURE_ID_CNT * sizeof(fd_pubkey_t);
404 30 : REQUIRE( fd_scratch_alloc_is_safe( alignof(fd_pubkey_t), features_sz ) );
405 30 : fd_pubkey_t * features =
406 30 : fd_scratch_alloc( alignof(fd_pubkey_t), features_sz );
407 :
408 30 : if( options->features ) {
409 18 : for( fd_feature_id_t const * id = fd_feature_iter_init();
410 5346 : !fd_feature_iter_done( id );
411 5328 : id = fd_feature_iter_next( id ) ) {
412 5328 : if( fd_features_get( options->features, id ) == 0UL )
413 0 : features[ feature_cnt++ ] = id->id;
414 5328 : }
415 18 : }
416 :
417 : /* Allocate the account table */
418 :
419 30 : ulong default_funded_cnt = options->fund_initial_accounts;
420 :
421 30 : ulong default_funded_idx = genesis->accounts_len;
422 30 : REQUIRE( !__builtin_add_overflow( genesis->accounts_len, default_funded_cnt, &genesis->accounts_len ) );
423 27 : ulong feature_gate_idx = genesis->accounts_len;
424 27 : REQUIRE( !__builtin_add_overflow( genesis->accounts_len, feature_cnt, &genesis->accounts_len ) );
425 :
426 : /* Program account, programdata account, mint, and a fixed number of
427 : token accounts per primordial account. */
428 27 : int const token_enabled = !!options->token_program_elf;
429 27 : ulong token_cnt = 0UL;
430 27 : if( token_enabled ) {
431 3 : REQUIRE( !__builtin_mul_overflow( default_funded_cnt, FD_GENESIS_TOKEN_ACCOUNTS_PER_ACCOUNT, &token_cnt ) );
432 3 : REQUIRE( !__builtin_add_overflow( token_cnt, 3UL, &token_cnt ) );
433 3 : }
434 27 : ulong token_idx = genesis->accounts_len;
435 27 : REQUIRE( !__builtin_add_overflow( genesis->accounts_len, token_cnt, &genesis->accounts_len ) );
436 :
437 27 : ulong accounts_sz;
438 27 : REQUIRE( !__builtin_mul_overflow( genesis->accounts_len, sizeof(fd_genesis_account_pair_t), &accounts_sz ) );
439 24 : REQUIRE( fd_scratch_alloc_is_safe( alignof(fd_genesis_account_pair_t), accounts_sz ) );
440 :
441 21 : genesis->accounts = fd_scratch_alloc( alignof(fd_genesis_account_pair_t),
442 21 : accounts_sz );
443 21 : fd_memset( genesis->accounts, 0, accounts_sz );
444 :
445 21 : genesis->accounts[ faucet_account_index ] = faucet_account;
446 21 : genesis->accounts[ identity_account_index ] = identity_account;
447 21 : genesis->accounts[ stake_account_index ] = (fd_genesis_account_pair_t) {
448 21 : .key = options->stake_pubkey,
449 21 : .account = (fd_genesis_account_t) {
450 21 : .lamports = fd_ulong_max( stake_state_min_bal, options->vote_account_stake ),
451 21 : .data_len = FD_STAKE_STATE_SZ,
452 21 : .data = stake_data,
453 21 : .owner = fd_solana_stake_program_id
454 21 : }
455 21 : };
456 21 : genesis->accounts[ vote_account_index ] = (fd_genesis_account_pair_t) {
457 21 : .key = options->vote_pubkey,
458 21 : .account = (fd_genesis_account_t) {
459 21 : .lamports = vote_min_bal,
460 21 : .data_len = FD_VOTE_STATE_V4_SZ,
461 21 : .data = vote_state_data,
462 21 : .owner = fd_solana_vote_program_id
463 21 : }
464 21 : };
465 :
466 : /* Set up primordial accounts */
467 :
468 21 : ulong default_funded_balance = options->fund_initial_amount_lamports;
469 213 : for( ulong j=0UL; j<default_funded_cnt; j++ ) {
470 192 : fd_genesis_account_pair_t * pair = &genesis->accounts[ default_funded_idx+j ];
471 :
472 192 : uchar privkey[ 32 ] = {0};
473 192 : FD_STORE( ulong, privkey, j );
474 192 : fd_sha512_t sha[1];
475 192 : fd_ed25519_public_from_private( pair->key.key, privkey, sha );
476 :
477 192 : pair->account = (fd_genesis_account_t) {
478 192 : .lamports = default_funded_balance,
479 192 : .data_len = 0UL,
480 192 : .owner = fd_solana_system_program_id
481 192 : };
482 192 : }
483 :
484 21 : #define FEATURE_ENABLED_SZ 9UL
485 21 : static const uchar feature_enabled_data[ FEATURE_ENABLED_SZ ] = { 1, 0, 0, 0, 0, 0, 0, 0, 0 };
486 21 : ulong default_feature_enabled_balance = fd_rent_exempt_minimum_balance( &genesis->rent, FEATURE_ENABLED_SZ );
487 :
488 : /* Set up feature gate accounts */
489 21 : for( ulong j=0UL; j<feature_cnt; j++ ) {
490 0 : fd_genesis_account_pair_t * pair = &genesis->accounts[ feature_gate_idx+j ];
491 :
492 0 : pair->key = features[ j ];
493 0 : pair->account = (fd_genesis_account_t) {
494 0 : .lamports = default_feature_enabled_balance,
495 0 : .data_len = FEATURE_ENABLED_SZ,
496 0 : .data = (uchar *)feature_enabled_data,
497 0 : .owner = fd_solana_feature_program_id
498 0 : };
499 0 : }
500 21 : #undef FEATURE_ENABLED_SZ
501 :
502 : /* Deploy an SPL Token compatible program and set up the token
503 : accounts */
504 :
505 21 : if( token_enabled ) {
506 3 : ulong const elf_sz = options->token_program_elf_sz;
507 3 : ulong const token_cnt_per_owner = FD_GENESIS_TOKEN_ACCOUNTS_PER_ACCOUNT;
508 :
509 : /* Program account: UpgradeableLoaderState::Program { programdata_address } */
510 :
511 3 : fd_pubkey_t programdata_addr[1];
512 3 : uchar const * seed = fd_solana_spl_token_id.uc;
513 3 : ulong const seedsz = sizeof(fd_pubkey_t);
514 3 : uchar bump;
515 3 : uint custom_err;
516 3 : REQUIRE( FD_PUBKEY_SUCCESS==fd_pubkey_find_program_address( &fd_solana_bpf_loader_upgradeable_program_id,
517 3 : 1UL, &seed, &seedsz,
518 3 : programdata_addr, &bump, &custom_err ) );
519 :
520 3 : REQUIRE( fd_scratch_alloc_is_safe( 1UL, SIZE_OF_PROGRAM ) );
521 3 : uchar * program_data = fd_scratch_alloc( 1UL, SIZE_OF_PROGRAM );
522 3 : fd_memset( program_data, 0, SIZE_OF_PROGRAM );
523 3 : FD_STORE( uint, program_data, 2U ); /* UpgradeableLoaderState::Program */
524 3 : fd_memcpy( program_data+4UL, programdata_addr->uc, sizeof(fd_pubkey_t) );
525 :
526 3 : genesis->accounts[ token_idx++ ] = (fd_genesis_account_pair_t) {
527 3 : .key = fd_solana_spl_token_id,
528 3 : .account = (fd_genesis_account_t) {
529 3 : .lamports = fd_rent_exempt_minimum_balance( &genesis->rent, SIZE_OF_PROGRAM ),
530 3 : .data_len = SIZE_OF_PROGRAM,
531 3 : .data = program_data,
532 3 : .owner = fd_solana_bpf_loader_upgradeable_program_id,
533 3 : .executable = 1
534 3 : }
535 3 : };
536 :
537 : /* ProgramData account: UpgradeableLoaderState::ProgramData {
538 : slot, upgrade_authority_address } followed by the ELF. The
539 : metadata is always padded out to the size of a Some(pubkey)
540 : authority, matching Agave. */
541 :
542 3 : ulong programdata_sz;
543 3 : REQUIRE( !__builtin_add_overflow( elf_sz, PROGRAMDATA_METADATA_SIZE, &programdata_sz ) );
544 3 : REQUIRE( fd_scratch_alloc_is_safe( 1UL, programdata_sz ) );
545 3 : uchar * programdata = fd_scratch_alloc( 1UL, programdata_sz );
546 3 : fd_memset( programdata, 0, PROGRAMDATA_METADATA_SIZE );
547 3 : FD_STORE( uint, programdata, 3U ); /* UpgradeableLoaderState::ProgramData */
548 3 : fd_memcpy( programdata+PROGRAMDATA_METADATA_SIZE, options->token_program_elf, elf_sz );
549 :
550 3 : genesis->accounts[ token_idx++ ] = (fd_genesis_account_pair_t) {
551 3 : .key = *programdata_addr,
552 3 : .account = (fd_genesis_account_t) {
553 3 : .lamports = fd_rent_exempt_minimum_balance( &genesis->rent, programdata_sz ),
554 3 : .data_len = programdata_sz,
555 3 : .data = programdata,
556 3 : .owner = fd_solana_bpf_loader_upgradeable_program_id
557 3 : }
558 3 : };
559 :
560 : /* Mint with no mint or freeze authority. Supply does not need to
561 : be consistent with the token accounts for transfers to work, but
562 : keep it honest anyway. */
563 :
564 3 : fd_pubkey_t mint_addr[1];
565 3 : fd_genesis_token_mint_address( mint_addr );
566 :
567 3 : REQUIRE( fd_scratch_alloc_is_safe( 1UL, SPL_TOKEN_MINT_SZ ) );
568 3 : uchar * mint_data = fd_scratch_alloc( 1UL, SPL_TOKEN_MINT_SZ );
569 3 : fd_memset( mint_data, 0, SPL_TOKEN_MINT_SZ );
570 3 : ulong token_account_cnt;
571 3 : ulong token_supply;
572 3 : REQUIRE( !__builtin_mul_overflow( token_cnt_per_owner, default_funded_cnt, &token_account_cnt ) );
573 3 : REQUIRE( !__builtin_mul_overflow( token_account_cnt, FD_GENESIS_TOKEN_AMOUNT, &token_supply ) );
574 3 : FD_STORE( ulong, mint_data+36UL, token_supply ); /* supply */
575 3 : mint_data[ 45 ] = 1; /* is_initialized */
576 :
577 3 : genesis->accounts[ token_idx++ ] = (fd_genesis_account_pair_t) {
578 3 : .key = *mint_addr,
579 3 : .account = (fd_genesis_account_t) {
580 3 : .lamports = fd_rent_exempt_minimum_balance( &genesis->rent, SPL_TOKEN_MINT_SZ ),
581 3 : .data_len = SPL_TOKEN_MINT_SZ,
582 3 : .data = mint_data,
583 3 : .owner = fd_solana_spl_token_id
584 3 : }
585 3 : };
586 :
587 : /* Initialized token accounts owned by each primordial account, all
588 : holding the same balance of the mint above. */
589 :
590 3 : ulong token_accounts_sz;
591 3 : REQUIRE( !__builtin_mul_overflow( token_account_cnt, SPL_TOKEN_ACCOUNT_SZ, &token_accounts_sz ) );
592 3 : REQUIRE( fd_scratch_alloc_is_safe( 1UL, token_accounts_sz ) );
593 3 : uchar * token_accounts = fd_scratch_alloc( 1UL, token_accounts_sz );
594 3 : fd_memset( token_accounts, 0, token_accounts_sz );
595 :
596 3 : ulong const token_min_bal = fd_rent_exempt_minimum_balance( &genesis->rent, SPL_TOKEN_ACCOUNT_SZ );
597 51 : for( ulong j=0UL; j<default_funded_cnt; j++ ) {
598 144 : for( ulong k=0UL; k<token_cnt_per_owner; k++ ) {
599 96 : uchar * data = token_accounts + (token_cnt_per_owner*j+k)*SPL_TOKEN_ACCOUNT_SZ;
600 96 : fd_memcpy( data, mint_addr->uc, sizeof(fd_pubkey_t) );
601 96 : fd_memcpy( data+32UL, genesis->accounts[ default_funded_idx+j ].key.uc, sizeof(fd_pubkey_t) );
602 96 : FD_STORE( ulong, data+64UL, FD_GENESIS_TOKEN_AMOUNT ); /* amount */
603 96 : data[ 108 ] = 1; /* state = Initialized */
604 :
605 96 : fd_pubkey_t key[1];
606 96 : fd_genesis_token_account_address( key, j, k );
607 96 : genesis->accounts[ token_idx++ ] = (fd_genesis_account_pair_t) {
608 96 : .key = *key,
609 96 : .account = (fd_genesis_account_t) {
610 96 : .lamports = token_min_bal,
611 96 : .data_len = SPL_TOKEN_ACCOUNT_SZ,
612 96 : .data = data,
613 96 : .owner = fd_solana_spl_token_id
614 96 : }
615 96 : };
616 96 : }
617 48 : }
618 :
619 3 : REQUIRE( token_idx==genesis->accounts_len );
620 3 : }
621 :
622 : /* Sort and check for duplicates */
623 :
624 21 : sort_acct_inplace( genesis->accounts, genesis->accounts_len );
625 :
626 381 : for( ulong j=1UL; j < genesis->accounts_len; j++ ) {
627 360 : if( 0==memcmp( genesis->accounts[j-1].key.ul, genesis->accounts[j].key.ul, sizeof(fd_pubkey_t) ) ) {
628 0 : char dup_cstr[ FD_BASE58_ENCODED_32_SZ ];
629 0 : fd_base58_encode_32( genesis->accounts[j].key.uc, NULL, dup_cstr );
630 0 : FD_LOG_WARNING(( "Account %s is duplicate", dup_cstr ));
631 0 : return 0UL;
632 0 : }
633 360 : }
634 :
635 : /* Serialize bincode blob */
636 :
637 21 : ulong encoded_sz = genesis_encode( genesis, (uchar *)buf, bufsz );
638 21 : if( FD_UNLIKELY( !encoded_sz ) ) {
639 6 : FD_LOG_WARNING(( "Failed to encode genesis blob (bufsz=%lu)", bufsz ));
640 6 : return 0UL;
641 6 : }
642 15 : return encoded_sz;
643 :
644 21 : # undef REQUIRE
645 21 : }
646 :
647 : ulong
648 : fd_genesis_create( void * buf,
649 : ulong bufsz,
650 30 : fd_genesis_options_t const * options ) {
651 30 : fd_scratch_push();
652 30 : ulong ret = genesis_create( buf, bufsz, options );
653 30 : fd_scratch_pop();
654 30 : return ret;
655 30 : }
|