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