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 = 0;
267 0 : genesis->poh_config.hashes_per_tick = 0UL;
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_DEFAULT_PARAMS;
289 :
290 : /* Create inflation configuration */
291 :
292 0 : genesis->inflation = (fd_inflation_t) {
293 0 : .initial = 0.08,
294 0 : .terminal = 0.015,
295 0 : .taper = 0.15,
296 0 : .foundation = 0.05,
297 0 : .foundation_term = 7.0,
298 0 : };
299 :
300 : /* Create epoch schedule */
301 : /* TODO The epoch schedule should be configurable! */
302 :
303 : /* If warmup is enabled:
304 : MINIMUM_SLOTS_PER_EPOCH = 32
305 : first_normal_epoch = log2( slots_per_epoch ) - log2( MINIMUM_SLOTS_PER_EPOCH )
306 : first_normal_slot = MINIMUM_SLOTS_PER_EPOCH * ( 2^( first_normal_epoch ) - 1 )
307 : */
308 :
309 0 : genesis->epoch_schedule = (fd_epoch_schedule_t) {
310 0 : .slots_per_epoch = 8192UL,
311 0 : .leader_schedule_slot_offset = 8192UL,
312 0 : .warmup = fd_uchar_if( options->warmup_epochs, 1, 0 ),
313 0 : .first_normal_epoch = fd_ulong_if( options->warmup_epochs, 8UL, 0UL ),
314 0 : .first_normal_slot = fd_ulong_if( options->warmup_epochs, 8160UL, 0UL ),
315 0 : };
316 :
317 : /* Create faucet account */
318 :
319 0 : fd_genesis_account_pair_t const faucet_account = {
320 0 : .key = options->faucet_pubkey,
321 0 : .account = {
322 0 : .lamports = options->faucet_balance,
323 0 : .owner = fd_solana_system_program_id
324 0 : }
325 0 : };
326 0 : ulong const faucet_account_index = genesis->accounts_len++;
327 :
328 : /* Create identity account (vote authority, withdraw authority) */
329 :
330 0 : fd_genesis_account_pair_t const identity_account = {
331 0 : .key = options->identity_pubkey,
332 0 : .account = {
333 0 : .lamports = 500000000000UL /* 500 SOL */,
334 0 : .owner = fd_solana_system_program_id
335 0 : }
336 0 : };
337 0 : ulong const identity_account_index = genesis->accounts_len++;
338 :
339 : /* Create vote account */
340 :
341 0 : ulong const vote_account_index = genesis->accounts_len++;
342 :
343 0 : uchar vote_state_data[ FD_VOTE_STATE_V4_SZ ] = {0};
344 :
345 0 : FD_SCRATCH_SCOPE_BEGIN {
346 0 : fd_vote_state_versioned_t versioned[1];
347 0 : fd_vote_state_versioned_new( versioned, fd_vote_state_versioned_enum_v4 );
348 :
349 0 : fd_vote_state_v4_t * vote_state = &versioned->v4;
350 0 : vote_state->node_pubkey = options->identity_pubkey;
351 0 : vote_state->authorized_withdrawer = options->identity_pubkey;
352 0 : vote_state->inflation_rewards_collector = options->vote_pubkey;
353 0 : vote_state->block_revenue_collector = options->identity_pubkey;
354 0 : vote_state->inflation_rewards_commission_bps = 10000;
355 0 : vote_state->block_revenue_commission_bps = 0;
356 0 : vote_state->has_bls_pubkey_compressed = 1;
357 0 : fd_memcpy( vote_state->bls_pubkey_compressed, options->bls_pubkey, sizeof(options->bls_pubkey) );
358 :
359 0 : fd_vote_authorized_voter_t * voter = fd_vote_authorized_voters_pool_ele_acquire( vote_state->authorized_voters.pool );
360 0 : *voter = (fd_vote_authorized_voter_t) {
361 0 : .epoch = 0UL,
362 0 : .pubkey = options->identity_pubkey,
363 0 : .prio = options->identity_pubkey.uc[0],
364 0 : };
365 0 : fd_vote_authorized_voters_treap_ele_insert( vote_state->authorized_voters.treap, voter, vote_state->authorized_voters.pool );
366 :
367 0 : REQUIRE( !fd_vote_state_versioned_serialize( versioned, vote_state_data, sizeof(vote_state_data) ) );
368 0 : }
369 0 : FD_SCRATCH_SCOPE_END;
370 :
371 : /* Create stake account */
372 :
373 0 : ulong const stake_account_index = genesis->accounts_len++;
374 :
375 0 : uchar stake_data[ FD_STAKE_STATE_SZ ] = {0};
376 :
377 0 : ulong stake_state_min_bal = fd_rent_exempt_minimum_balance( &genesis->rent, FD_STAKE_STATE_SZ );
378 0 : ulong vote_min_bal = fd_rent_exempt_minimum_balance( &genesis->rent, FD_VOTE_STATE_V4_SZ ) +
379 0 : FD_GENESIS_VAT_MINIMUM_LAMPORTS;
380 :
381 0 : do {
382 0 : FD_STORE( fd_stake_state_t, stake_data, ((fd_stake_state_t) {
383 0 : .stake_type = FD_STAKE_STATE_STAKE,
384 0 : .stake = {
385 0 : .meta = {
386 0 : .rent_exempt_reserve = stake_state_min_bal,
387 0 : .staker = options->identity_pubkey,
388 0 : .withdrawer = options->identity_pubkey,
389 0 : },
390 0 : .stake = (fd_stake_t) {
391 0 : .delegation = (fd_delegation_t) {
392 0 : .voter_pubkey = options->vote_pubkey,
393 0 : .stake = fd_ulong_max( stake_state_min_bal, options->vote_account_stake ),
394 0 : .activation_epoch = ULONG_MAX, /* bootstrap stake denoted with ULONG_MAX */
395 0 : .deactivation_epoch = ULONG_MAX,
396 0 : .warmup_cooldown_rate = 0.25
397 0 : },
398 0 : .credits_observed = 0UL
399 0 : }
400 0 : }
401 0 : }) );
402 0 : } while(0);
403 :
404 : /* Read enabled features */
405 :
406 0 : ulong feature_cnt = 0UL;
407 0 : ulong const features_sz = FD_FEATURE_ID_CNT * sizeof(fd_pubkey_t);
408 0 : REQUIRE( fd_scratch_alloc_is_safe( alignof(fd_pubkey_t), features_sz ) );
409 0 : fd_pubkey_t * features =
410 0 : fd_scratch_alloc( alignof(fd_pubkey_t), features_sz );
411 :
412 0 : if( options->features ) {
413 0 : for( fd_feature_id_t const * id = fd_feature_iter_init();
414 0 : !fd_feature_iter_done( id );
415 0 : id = fd_feature_iter_next( id ) ) {
416 0 : if( fd_features_get( options->features, id ) == 0UL )
417 0 : features[ feature_cnt++ ] = id->id;
418 0 : }
419 0 : }
420 :
421 : /* Allocate the account table */
422 :
423 0 : ulong default_funded_cnt = options->fund_initial_accounts;
424 :
425 0 : ulong default_funded_idx = genesis->accounts_len;
426 0 : REQUIRE( !__builtin_add_overflow( genesis->accounts_len, default_funded_cnt, &genesis->accounts_len ) );
427 0 : ulong feature_gate_idx = genesis->accounts_len;
428 0 : REQUIRE( !__builtin_add_overflow( genesis->accounts_len, feature_cnt, &genesis->accounts_len ) );
429 :
430 : /* Program account, programdata account, mint, and a fixed number of
431 : token accounts per primordial account. */
432 0 : int const token_enabled = !!options->token_program_elf;
433 0 : ulong token_cnt = 0UL;
434 0 : if( token_enabled ) {
435 0 : REQUIRE( !__builtin_mul_overflow( default_funded_cnt, FD_GENESIS_TOKEN_ACCOUNTS_PER_ACCOUNT, &token_cnt ) );
436 0 : REQUIRE( !__builtin_add_overflow( token_cnt, 3UL, &token_cnt ) );
437 0 : }
438 0 : ulong token_idx = genesis->accounts_len;
439 0 : REQUIRE( !__builtin_add_overflow( genesis->accounts_len, token_cnt, &genesis->accounts_len ) );
440 :
441 : /* Genesis certificate and epoch inflation account */
442 0 : ulong alpenglow_idx = genesis->accounts_len;
443 0 : REQUIRE( !__builtin_add_overflow( genesis->accounts_len, 2UL, &genesis->accounts_len ) );
444 :
445 0 : ulong accounts_sz;
446 0 : REQUIRE( !__builtin_mul_overflow( genesis->accounts_len, sizeof(fd_genesis_account_pair_t), &accounts_sz ) );
447 0 : REQUIRE( fd_scratch_alloc_is_safe( alignof(fd_genesis_account_pair_t), accounts_sz ) );
448 :
449 0 : genesis->accounts = fd_scratch_alloc( alignof(fd_genesis_account_pair_t),
450 0 : accounts_sz );
451 0 : fd_memset( genesis->accounts, 0, accounts_sz );
452 :
453 0 : genesis->accounts[ faucet_account_index ] = faucet_account;
454 0 : genesis->accounts[ identity_account_index ] = identity_account;
455 0 : genesis->accounts[ stake_account_index ] = (fd_genesis_account_pair_t) {
456 0 : .key = options->stake_pubkey,
457 0 : .account = (fd_genesis_account_t) {
458 0 : .lamports = fd_ulong_max( stake_state_min_bal, options->vote_account_stake ),
459 0 : .data_len = FD_STAKE_STATE_SZ,
460 0 : .data = stake_data,
461 0 : .owner = fd_solana_stake_program_id
462 0 : }
463 0 : };
464 0 : genesis->accounts[ vote_account_index ] = (fd_genesis_account_pair_t) {
465 0 : .key = options->vote_pubkey,
466 0 : .account = (fd_genesis_account_t) {
467 0 : .lamports = vote_min_bal,
468 0 : .data_len = FD_VOTE_STATE_V4_SZ,
469 0 : .data = vote_state_data,
470 0 : .owner = fd_solana_vote_program_id
471 0 : }
472 0 : };
473 :
474 : /* Set up primordial accounts */
475 :
476 0 : ulong default_funded_balance = options->fund_initial_amount_lamports;
477 0 : for( ulong j=0UL; j<default_funded_cnt; j++ ) {
478 0 : fd_genesis_account_pair_t * pair = &genesis->accounts[ default_funded_idx+j ];
479 :
480 0 : uchar privkey[ 32 ] = {0};
481 0 : FD_STORE( ulong, privkey, j );
482 0 : fd_sha512_t sha[1];
483 0 : fd_ed25519_public_from_private( pair->key.key, privkey, sha );
484 :
485 0 : pair->account = (fd_genesis_account_t) {
486 0 : .lamports = default_funded_balance,
487 0 : .data_len = 0UL,
488 0 : .owner = fd_solana_system_program_id
489 0 : };
490 0 : }
491 :
492 0 : #define FEATURE_ENABLED_SZ 9UL
493 0 : static const uchar feature_enabled_data[ FEATURE_ENABLED_SZ ] = { 1, 0, 0, 0, 0, 0, 0, 0, 0 };
494 0 : ulong default_feature_enabled_balance = fd_rent_exempt_minimum_balance( &genesis->rent, FEATURE_ENABLED_SZ );
495 :
496 : /* Set up feature gate accounts */
497 0 : for( ulong j=0UL; j<feature_cnt; j++ ) {
498 0 : fd_genesis_account_pair_t * pair = &genesis->accounts[ feature_gate_idx+j ];
499 :
500 0 : pair->key = features[ j ];
501 0 : pair->account = (fd_genesis_account_t) {
502 0 : .lamports = default_feature_enabled_balance,
503 0 : .data_len = FEATURE_ENABLED_SZ,
504 0 : .data = (uchar *)feature_enabled_data,
505 0 : .owner = fd_solana_feature_program_id
506 0 : };
507 0 : }
508 0 : #undef FEATURE_ENABLED_SZ
509 :
510 : /* Alpenglow at genesis skips the migration. Like Agave, add a fake
511 : genesis certificate for slot 0 and an empty epoch inflation state.
512 : https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/genesis_utils.rs#L340-L364 */
513 :
514 0 : uchar genesis_cert_data [ FD_GENESIS_CERT_SZ ] = {0};
515 0 : uchar epoch_inflation_data[ FD_EPOCH_INFLATION_ACCOUNT_SOME ];
516 0 : do {
517 : /* WireBlockCertMessage { block: { slot: 0, block_id: 0 },
518 : signature: { signature: 0, bitmap: encode_base2( [] ) } }
519 : bincode encoded. The bitmap is a Vec<u8> holding the base2
520 : version byte (0) and a u16 bit count (0). */
521 0 : FD_STORE( ulong, genesis_cert_data+232UL, 3UL );
522 :
523 0 : fd_pubkey_t genesis_cert_addr;
524 0 : fd_alpenglow_pda( "carlgration", &genesis_cert_addr );
525 0 : genesis->accounts[ alpenglow_idx ] = (fd_genesis_account_pair_t) {
526 0 : .key = genesis_cert_addr,
527 0 : .account = (fd_genesis_account_t) {
528 0 : .lamports = fd_rent_exempt_minimum_balance( &genesis->rent, FD_GENESIS_CERT_SZ ),
529 0 : .data_len = FD_GENESIS_CERT_SZ,
530 0 : .data = genesis_cert_data,
531 0 : .owner = fd_solana_system_program_id
532 0 : }
533 0 : };
534 :
535 0 : fd_epoch_inflation_account_state_t epoch_inflation = { .current = { .slots_per_epoch = genesis->epoch_schedule.slots_per_epoch } };
536 0 : ulong epoch_inflation_sz = fd_epoch_inflation_account_encode( epoch_inflation_data, &epoch_inflation );
537 0 : genesis->accounts[ alpenglow_idx+1UL ] = (fd_genesis_account_pair_t) {
538 0 : .key = fd_epoch_inflation_account_address(),
539 0 : .account = (fd_genesis_account_t) {
540 0 : .lamports = fd_ulong_max( fd_rent_exempt_minimum_balance( &genesis->rent, epoch_inflation_sz ), 1UL ),
541 0 : .data_len = epoch_inflation_sz,
542 0 : .data = epoch_inflation_data,
543 0 : .owner = fd_solana_system_program_id
544 0 : }
545 0 : };
546 0 : } while(0);
547 :
548 : /* Deploy an SPL Token compatible program and set up the token
549 : accounts */
550 :
551 0 : if( token_enabled ) {
552 0 : ulong const elf_sz = options->token_program_elf_sz;
553 0 : ulong const token_cnt_per_owner = FD_GENESIS_TOKEN_ACCOUNTS_PER_ACCOUNT;
554 :
555 : /* Program account: UpgradeableLoaderState::Program { programdata_address } */
556 :
557 0 : fd_pubkey_t programdata_addr[1];
558 0 : uchar const * seed = fd_solana_spl_token_id.uc;
559 0 : ulong const seedsz = sizeof(fd_pubkey_t);
560 0 : uchar bump;
561 0 : uint custom_err;
562 0 : REQUIRE( FD_PUBKEY_SUCCESS==fd_pubkey_find_program_address( &fd_solana_bpf_loader_upgradeable_program_id,
563 0 : 1UL, &seed, &seedsz,
564 0 : programdata_addr, &bump, &custom_err ) );
565 :
566 0 : REQUIRE( fd_scratch_alloc_is_safe( 1UL, SIZE_OF_PROGRAM ) );
567 0 : uchar * program_data = fd_scratch_alloc( 1UL, SIZE_OF_PROGRAM );
568 0 : fd_memset( program_data, 0, SIZE_OF_PROGRAM );
569 0 : FD_STORE( uint, program_data, 2U ); /* UpgradeableLoaderState::Program */
570 0 : fd_memcpy( program_data+4UL, programdata_addr->uc, sizeof(fd_pubkey_t) );
571 :
572 0 : genesis->accounts[ token_idx++ ] = (fd_genesis_account_pair_t) {
573 0 : .key = fd_solana_spl_token_id,
574 0 : .account = (fd_genesis_account_t) {
575 0 : .lamports = fd_rent_exempt_minimum_balance( &genesis->rent, SIZE_OF_PROGRAM ),
576 0 : .data_len = SIZE_OF_PROGRAM,
577 0 : .data = program_data,
578 0 : .owner = fd_solana_bpf_loader_upgradeable_program_id,
579 0 : .executable = 1
580 0 : }
581 0 : };
582 :
583 : /* ProgramData account: UpgradeableLoaderState::ProgramData {
584 : slot, upgrade_authority_address } followed by the ELF. The
585 : metadata is always padded out to the size of a Some(pubkey)
586 : authority, matching Agave. */
587 :
588 0 : ulong programdata_sz;
589 0 : REQUIRE( !__builtin_add_overflow( elf_sz, PROGRAMDATA_METADATA_SIZE, &programdata_sz ) );
590 0 : REQUIRE( fd_scratch_alloc_is_safe( 1UL, programdata_sz ) );
591 0 : uchar * programdata = fd_scratch_alloc( 1UL, programdata_sz );
592 0 : fd_memset( programdata, 0, PROGRAMDATA_METADATA_SIZE );
593 0 : FD_STORE( uint, programdata, 3U ); /* UpgradeableLoaderState::ProgramData */
594 0 : fd_memcpy( programdata+PROGRAMDATA_METADATA_SIZE, options->token_program_elf, elf_sz );
595 :
596 0 : genesis->accounts[ token_idx++ ] = (fd_genesis_account_pair_t) {
597 0 : .key = *programdata_addr,
598 0 : .account = (fd_genesis_account_t) {
599 0 : .lamports = fd_rent_exempt_minimum_balance( &genesis->rent, programdata_sz ),
600 0 : .data_len = programdata_sz,
601 0 : .data = programdata,
602 0 : .owner = fd_solana_bpf_loader_upgradeable_program_id
603 0 : }
604 0 : };
605 :
606 : /* Mint with no mint or freeze authority. Supply does not need to
607 : be consistent with the token accounts for transfers to work, but
608 : keep it honest anyway. */
609 :
610 0 : fd_pubkey_t mint_addr[1];
611 0 : fd_genesis_token_mint_address( mint_addr );
612 :
613 0 : REQUIRE( fd_scratch_alloc_is_safe( 1UL, SPL_TOKEN_MINT_SZ ) );
614 0 : uchar * mint_data = fd_scratch_alloc( 1UL, SPL_TOKEN_MINT_SZ );
615 0 : fd_memset( mint_data, 0, SPL_TOKEN_MINT_SZ );
616 0 : ulong token_account_cnt;
617 0 : ulong token_supply;
618 0 : REQUIRE( !__builtin_mul_overflow( token_cnt_per_owner, default_funded_cnt, &token_account_cnt ) );
619 0 : REQUIRE( !__builtin_mul_overflow( token_account_cnt, FD_GENESIS_TOKEN_AMOUNT, &token_supply ) );
620 0 : FD_STORE( ulong, mint_data+36UL, token_supply ); /* supply */
621 0 : mint_data[ 45 ] = 1; /* is_initialized */
622 :
623 0 : genesis->accounts[ token_idx++ ] = (fd_genesis_account_pair_t) {
624 0 : .key = *mint_addr,
625 0 : .account = (fd_genesis_account_t) {
626 0 : .lamports = fd_rent_exempt_minimum_balance( &genesis->rent, SPL_TOKEN_MINT_SZ ),
627 0 : .data_len = SPL_TOKEN_MINT_SZ,
628 0 : .data = mint_data,
629 0 : .owner = fd_solana_spl_token_id
630 0 : }
631 0 : };
632 :
633 : /* Initialized token accounts owned by each primordial account, all
634 : holding the same balance of the mint above. */
635 :
636 0 : ulong token_accounts_sz;
637 0 : REQUIRE( !__builtin_mul_overflow( token_account_cnt, SPL_TOKEN_ACCOUNT_SZ, &token_accounts_sz ) );
638 0 : REQUIRE( fd_scratch_alloc_is_safe( 1UL, token_accounts_sz ) );
639 0 : uchar * token_accounts = fd_scratch_alloc( 1UL, token_accounts_sz );
640 0 : fd_memset( token_accounts, 0, token_accounts_sz );
641 :
642 0 : ulong const token_min_bal = fd_rent_exempt_minimum_balance( &genesis->rent, SPL_TOKEN_ACCOUNT_SZ );
643 0 : for( ulong j=0UL; j<default_funded_cnt; j++ ) {
644 0 : for( ulong k=0UL; k<token_cnt_per_owner; k++ ) {
645 0 : uchar * data = token_accounts + (token_cnt_per_owner*j+k)*SPL_TOKEN_ACCOUNT_SZ;
646 0 : fd_memcpy( data, mint_addr->uc, sizeof(fd_pubkey_t) );
647 0 : fd_memcpy( data+32UL, genesis->accounts[ default_funded_idx+j ].key.uc, sizeof(fd_pubkey_t) );
648 0 : FD_STORE( ulong, data+64UL, FD_GENESIS_TOKEN_AMOUNT ); /* amount */
649 0 : data[ 108 ] = 1; /* state = Initialized */
650 :
651 0 : fd_pubkey_t key[1];
652 0 : fd_genesis_token_account_address( key, j, k );
653 0 : genesis->accounts[ token_idx++ ] = (fd_genesis_account_pair_t) {
654 0 : .key = *key,
655 0 : .account = (fd_genesis_account_t) {
656 0 : .lamports = token_min_bal,
657 0 : .data_len = SPL_TOKEN_ACCOUNT_SZ,
658 0 : .data = data,
659 0 : .owner = fd_solana_spl_token_id
660 0 : }
661 0 : };
662 0 : }
663 0 : }
664 :
665 0 : REQUIRE( token_idx==alpenglow_idx );
666 0 : }
667 :
668 : /* Sort and check for duplicates */
669 :
670 0 : sort_acct_inplace( genesis->accounts, genesis->accounts_len );
671 :
672 0 : for( ulong j=1UL; j < genesis->accounts_len; j++ ) {
673 0 : if( 0==memcmp( genesis->accounts[j-1].key.ul, genesis->accounts[j].key.ul, sizeof(fd_pubkey_t) ) ) {
674 0 : char dup_cstr[ FD_BASE58_ENCODED_32_SZ ];
675 0 : fd_base58_encode_32( genesis->accounts[j].key.uc, NULL, dup_cstr );
676 0 : FD_LOG_WARNING(( "Account %s is duplicate", dup_cstr ));
677 0 : return 0UL;
678 0 : }
679 0 : }
680 :
681 : /* Serialize bincode blob */
682 :
683 0 : ulong encoded_sz = genesis_encode( genesis, (uchar *)buf, bufsz );
684 0 : if( FD_UNLIKELY( !encoded_sz ) ) {
685 0 : FD_LOG_WARNING(( "Failed to encode genesis blob (bufsz=%lu)", bufsz ));
686 0 : return 0UL;
687 0 : }
688 0 : return encoded_sz;
689 :
690 0 : # undef REQUIRE
691 0 : }
692 :
693 : ulong
694 : fd_genesis_create( void * buf,
695 : ulong bufsz,
696 0 : fd_genesis_options_t const * options ) {
697 0 : fd_scratch_push();
698 0 : ulong ret = genesis_create( buf, bufsz, options );
699 0 : fd_scratch_pop();
700 0 : return ret;
701 0 : }
|