Skip to content

Commit

Permalink
add fd_genesis_create API
Browse files Browse the repository at this point in the history
  • Loading branch information
riptl authored and ripatel-fd committed Apr 15, 2024
1 parent b0b667d commit 904860c
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 34 deletions.
6 changes: 6 additions & 0 deletions src/flamenco/genesis/Local.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ifdef FD_HAS_INT128
$(call add-hdrs,fd_genesis_create.h)
$(call add-objs,fd_genesis_create,fd_flamenco)
$(call make-unit-test,test_genesis_create,test_genesis_create,fd_flamenco fd_funk fd_ballet fd_util)
$(call run-unit-test,test_genesis_create)
endif
256 changes: 256 additions & 0 deletions src/flamenco/genesis/fd_genesis_create.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
#define FD_SCRATCH_USE_HANDHOLDING 1
#include "fd_genesis_create.h"

#include "../runtime/fd_system_ids.h"
#include "../runtime/program/fd_stake_program.h"
#include "../runtime/program/fd_vote_program.h"
#include "../runtime/sysvar/fd_sysvar_clock.h"
#include "../runtime/sysvar/fd_sysvar_rent.h"
#include "../types/fd_types.h"

static ulong
genesis_create( void * buf,
ulong bufsz,
uchar const * pod ) {

# define REQUIRE(c) \
do { \
if( FD_UNLIKELY( !(c) ) ) { \
FD_LOG_WARNING(( "FAIL: %s", #c )); \
return 0UL; \
} \
} while(0);

fd_pubkey_t identity_pubkey;
REQUIRE( fd_pod_query_pubkey( pod, "identity.pubkey", &identity_pubkey ) );

fd_pubkey_t faucet_pubkey;
REQUIRE( fd_pod_query_pubkey( pod, "faucet.pubkey", &faucet_pubkey ) );

fd_pubkey_t stake_pubkey;
REQUIRE( fd_pod_query_pubkey( pod, "stake.pubkey", &stake_pubkey ) );

fd_pubkey_t vote_pubkey;
REQUIRE( fd_pod_query_pubkey( pod, "vote.pubkey", &vote_pubkey ) );

fd_genesis_solana_t genesis[1];
fd_genesis_solana_new( genesis );

genesis->cluster_type = 3; /* development */

genesis->creation_time = fd_pod_query_ulong( pod, "creation_time", 0UL );
genesis->ticks_per_slot = fd_pod_query_ulong( pod, "ticks_per_slot", 0UL );
REQUIRE( genesis->ticks_per_slot );

ulong hashes_per_tick = fd_pod_query_ulong( pod, "hashes_per_tick", 0UL );
genesis->poh_config.has_hashes_per_tick = !!hashes_per_tick;
genesis->poh_config.hashes_per_tick = hashes_per_tick;

ulong target_tick_micros = fd_pod_query_ulong( pod, "target_tick_µs", 0UL );
REQUIRE( target_tick_micros );
genesis->poh_config.target_tick_duration = (fd_rust_duration_t) {
.seconds = target_tick_micros / 1000000UL,
.nanoseconds = (uint)( target_tick_micros % 1000000UL * 1000UL ),
};

/* Create fee rate governor */

genesis->fee_rate_governor = (fd_fee_rate_governor_t) {
.target_lamports_per_signature = 10000UL,
.target_signatures_per_slot = 20000UL,
.min_lamports_per_signature = 0UL,
.max_lamports_per_signature = 0UL,
.burn_percent = 50,
};

/* Create rent configuration */

genesis->rent = (fd_rent_t) {
.lamports_per_uint8_year = 3480,
.exemption_threshold = 2.0,
.burn_percent = 50,
};

/* Create epoch schedule */
/* TODO The epoch schedule should be configurable! */

genesis->epoch_schedule = (fd_epoch_schedule_t) {
.slots_per_epoch = 8192UL,
.leader_schedule_slot_offset = 8192UL,
.warmup = 0,
.first_normal_epoch = 0UL,
.first_normal_slot = 0UL,
};

/* Create faucet account */

fd_pubkey_account_pair_t const faucet_account = {
.account = {
.lamports = fd_pod_query_ulong( pod, "faucet.balance", 1000000000UL /* 1 SOL */ ),
.owner = fd_solana_system_program_id,
.rent_epoch = ULONG_MAX
}
};
ulong const faucet_account_index = genesis->accounts_len++;

/* Create identity account (vote authority, withdraw authority) */

fd_pubkey_account_pair_t const identity_account = {
.account = {
.lamports = 500000000000UL /* 500 SOL */,
.owner = fd_solana_system_program_id,
.rent_epoch = ULONG_MAX
}
};
ulong const identity_account_index = genesis->accounts_len++;

/* Create vote account */

ulong const vote_account_index = genesis->accounts_len++;

uchar vote_state_data[ FD_VOTE_STATE_V3_SZ ] = {0};
ulong vote_state_dlen = 0UL;

FD_SCRATCH_SCOPE_BEGIN {
fd_vote_state_versioned_t vsv[1];
fd_vote_state_versioned_new_disc( vsv, fd_vote_state_versioned_enum_current );

fd_vote_state_t * vs = &vsv->inner.current;
vs->node_pubkey = identity_pubkey;
vs->authorized_withdrawer = identity_pubkey;
vs->commission = 100;
vs->authorized_voters.pool = fd_vote_authorized_voters_pool_alloc ( fd_scratch_virtual() );
vs->authorized_voters.treap = fd_vote_authorized_voters_treap_alloc( fd_scratch_virtual() );

fd_vote_authorized_voter_t * ele =
fd_vote_authorized_voters_pool_ele_acquire( vs->authorized_voters.pool );
*ele = (fd_vote_authorized_voter_t) {
.epoch = 0UL,
.pubkey = identity_pubkey,
.prio = identity_pubkey.ul[0], /* treap prio */
};
fd_vote_authorized_voters_treap_ele_insert( vs->authorized_voters.treap, ele, vs->authorized_voters.pool );

fd_bincode_encode_ctx_t encode =
{ .data = vote_state_data,
.dataend = vote_state_data + sizeof(vote_state_data) };
REQUIRE( fd_vote_state_versioned_encode( vsv, &encode ) == FD_BINCODE_SUCCESS );
vote_state_dlen = (ulong)encode.data - (ulong)vote_state_data;
}
FD_SCRATCH_SCOPE_END;

/* Create stake account */

ulong const stake_account_index = genesis->accounts_len++;

uchar stake_data[ FD_STAKE_STATE_V2_SZ ];
ulong stake_dlen = 0UL;

do {
fd_stake_state_v2_t state[1];
fd_stake_state_v2_new_disc( state, fd_stake_state_v2_enum_stake );

ulong stake_state_min_bal = fd_rent_exempt_minimum_balance2( &genesis->rent, FD_STAKE_STATE_V2_SZ );

fd_stake_state_v2_stake_t * stake = &state->inner.stake;
stake->meta = (fd_stake_meta_t) {
.rent_exempt_reserve = stake_state_min_bal,
.authorized = {
.staker = identity_pubkey,
.withdrawer = identity_pubkey,
}
};
stake->stake = (fd_stake_t) {
.delegation = (fd_delegation_t) {
.voter_pubkey = vote_pubkey,
.stake = fd_ulong_max( stake_state_min_bal, 500000000UL /* 0.5 SOL */ ),
.activation_epoch = 0UL
},
.credits_observed = 0UL
};

fd_bincode_encode_ctx_t encode =
{ .data = stake_data,
.dataend = stake_data + sizeof(stake_data) };
REQUIRE( fd_stake_state_v2_encode( state, &encode ) == FD_BINCODE_SUCCESS );
stake_dlen = (ulong)encode.data - (ulong)stake_data;
} while(0);

/* Allocate the account table */

ulong default_funded_idx = genesis->accounts_len;
ulong default_funded_cnt = fd_pod_query_ulong( pod, "default_funded.cnt", 0UL );
genesis->accounts_len += default_funded_cnt;

genesis->accounts = fd_scratch_alloc( alignof(fd_pubkey_account_pair_t),
genesis->accounts_len * sizeof(fd_pubkey_account_pair_t) );
fd_memset( genesis->accounts, 0, genesis->accounts_len * sizeof(fd_pubkey_account_pair_t) );

genesis->accounts[ faucet_account_index ] = faucet_account;
genesis->accounts[ identity_account_index ] = identity_account;
genesis->accounts[ stake_account_index ] = (fd_pubkey_account_pair_t) {
.key = stake_pubkey,
.account = (fd_solana_account_t) {
.lamports = 0UL,
.data_len = stake_dlen,
.data = stake_data,
.owner = fd_solana_stake_program_id,
.rent_epoch = ULONG_MAX
}
};
genesis->accounts[ vote_account_index ] = (fd_pubkey_account_pair_t) {
.key = vote_pubkey,
.account = (fd_solana_account_t) {
.lamports = 0UL,
.data_len = vote_state_dlen,
.data = vote_state_data,
.owner = fd_solana_vote_program_id,
.rent_epoch = ULONG_MAX
}
};

/* Set up primordial accounts */

ulong default_funded_balance = fd_pod_query_ulong( pod, "default_funded.balance", 0UL );
for( ulong j=0UL; j<default_funded_cnt; j++ ) {
fd_pubkey_account_pair_t * pair = &genesis->accounts[ default_funded_idx+j ];

uchar privkey[ 32 ] = {0};
FD_STORE( ulong, privkey, j );
fd_sha512_t sha[1];
fd_ed25519_public_from_private( pair->key.key, privkey, sha );

pair->account = (fd_solana_account_t) {
.lamports = default_funded_balance,
.data_len = 0UL,
.owner = fd_solana_system_program_id,
.rent_epoch = ULONG_MAX
};
}

/* TODO make sure that none of the accounts are duplicate */

/* Serialize bincode blob */

fd_bincode_encode_ctx_t encode =
{ .data = buf,
.dataend = (uchar *)buf + bufsz };
int encode_err = fd_genesis_solana_encode( genesis, &encode );
if( FD_UNLIKELY( encode_err ) ) {
FD_LOG_WARNING(( "Failed to encode genesis blob (bufsz=%lu)", bufsz ));
return 0UL;
}
return (ulong)encode.data - (ulong)buf;

# undef REQUIRE
}

ulong
fd_genesis_create( void * buf,
ulong bufsz,
uchar const * pod ) {
fd_scratch_push();
ulong ret = genesis_create( buf, bufsz, pod );
fd_scratch_pop();
return ret;
}
35 changes: 35 additions & 0 deletions src/flamenco/genesis/fd_genesis_create.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef HEADER_fd_src_flamenco_genesis_fd_genesis_create_h
#define HEADER_fd_src_flamenco_genesis_fd_genesis_create_h

/* fd_genesis_create.h is a tool for creating Solana genesis blobs.
A genesis blob is used to bootstrap a Solana ledger. */

#include "../fd_flamenco_base.h"

FD_PROTOTYPES_BEGIN

/* fd_genesis_create creates a 'genesis.bin' compatible genesis blob.
(Bincode encoded fd_genesis_solana_t) [buf,bufsz) it the output
memory region into which the genesis blob will be written. pod
points to an fd_pod containing the genesis configuration parameters.
(Refer to fd_genesis.c code for the pod layout, there are no docs.)
Returns the number of bytes in the output memory region used on
success. On failure, returns 0UL and logs reason for error.
Assumes that caller is attached to an fd_scratch with sufficient
memory to buffer intermediate data (8192 + 128*n space, 2 frames).
THIS METHOD IS NOT SAFE FOR PRODUCTION USE.
It is intended for development only. */

ulong
fd_genesis_create( void * buf,
ulong bufsz,
uchar const * pod );

/* TODO Add method to estimate the scratch and genesis blob size given a pod */

FD_PROTOTYPES_END

#endif /* HEADER_fd_src_flamenco_genesis_fd_genesis_create_h */
78 changes: 78 additions & 0 deletions src/flamenco/genesis/test_genesis_create.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include "fd_genesis_create.h"
#include "../types/fd_types.h"

#define BUFSZ (32768UL)

int
main( int argc,
char ** argv ) {
fd_boot( &argc, &argv );

/* Suppress warning logs */

int log_level = fd_log_level_logfile();
fd_log_level_logfile_set( fd_int_max( log_level, 4 ) );

static uchar scratch_smem[ 8192 ];
ulong scratch_fmem[ 4 ];
fd_scratch_attach( scratch_smem, scratch_fmem,
sizeof(scratch_smem), sizeof(scratch_fmem)/sizeof(ulong) );

static uchar pod_mem[ 8192 ];
uchar * pod = fd_pod_join( fd_pod_new( pod_mem, sizeof(pod_mem) ) );

/* Minimal configuration */

FD_TEST( !fd_genesis_create( NULL, 0UL, pod ) );
fd_pubkey_t identity_pubkey = { .ul = { 0, 0, 0, 1 } };
fd_pod_insert_pubkey( pod, "identity.pubkey", &identity_pubkey );

FD_TEST( !fd_genesis_create( NULL, 0UL, pod ) );
fd_pubkey_t faucet_pubkey = { .ul = { 0, 0, 0, 2 } };
fd_pod_insert_pubkey( pod, "faucet.pubkey", &faucet_pubkey );

FD_TEST( !fd_genesis_create( NULL, 0UL, pod ) );
fd_pubkey_t stake_pubkey = { .ul = { 0, 0, 0, 3 } };
fd_pod_insert_pubkey( pod, "stake.pubkey", &stake_pubkey );

FD_TEST( !fd_genesis_create( NULL, 0UL, pod ) );
fd_pubkey_t vote_pubkey = { .ul = { 0, 0, 0, 4 } };
fd_pod_insert_pubkey( pod, "vote.pubkey", &vote_pubkey );

FD_TEST( !fd_genesis_create( NULL, 0UL, pod ) );
fd_pod_insert_ulong( pod, "creation_time", 123UL );
fd_pod_insert_ulong( pod, "ticks_per_slot", 64UL );

FD_TEST( !fd_genesis_create( NULL, 0UL, pod ) );
fd_pod_insert_ulong( pod, "target_tick_µs", 6250UL );

/* Buffer too small */

FD_TEST( !fd_genesis_create( NULL, 0UL, pod ) );

/* No more warnings expected */

fd_log_level_logfile_set( log_level );

/* Serialize to buffer */

static uchar result_mem[ BUFSZ ];
ulong result_sz = fd_genesis_create( result_mem, sizeof(result_mem), pod );
FD_TEST( result_sz );

/* Now try adding a few accounts */

fd_pod_insert_ulong( pod, "default_funded.cnt", 16UL );
result_sz = fd_genesis_create( result_mem, sizeof(result_mem), pod );
FD_TEST( result_sz );

/* TODO load this into a Firedancer runtime and verify the resulting slot context */

FD_TEST( fd_pod_delete( fd_pod_leave ( pod ) )==pod_mem );

FD_LOG_NOTICE(( "pass" ));

fd_scratch_detach( NULL );
fd_halt();
return 0;
}
2 changes: 2 additions & 0 deletions src/flamenco/runtime/program/fd_stake_program.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

#include "../context/fd_exec_instr_ctx.h"

#define FD_STAKE_STATE_V2_SZ (200UL)

FD_PROTOTYPES_BEGIN

/* fd_stake_program_execute is the instruction processing entrypoint
Expand Down
Loading

0 comments on commit 904860c

Please sign in to comment.