diff --git a/src/discof/genesis/fd_genesi_tile.c b/src/discof/genesis/fd_genesi_tile.c index 373e35879d..154deb9088 100644 --- a/src/discof/genesis/fd_genesi_tile.c +++ b/src/discof/genesis/fd_genesi_tile.c @@ -4,6 +4,7 @@ #include "fd_genesis_client.h" #include "../../disco/topo/fd_topo.h" #include "../../ballet/sha256/fd_sha256.h" +#include "../../flamenco/runtime/fd_genesis_parse.h" #include "../../flamenco/accdb/fd_accdb_admin.h" #include "../../flamenco/accdb/fd_accdb_impl_v1.h" #include "../../flamenco/runtime/fd_hashes.h" @@ -25,9 +26,6 @@ #include "generated/fd_genesi_tile_seccomp.h" - -#define GENESIS_MAX_SZ (10UL*1024UL*1024UL) /* 10 MiB */ - static void * bz2_malloc( void * opaque, int items, @@ -66,9 +64,8 @@ struct fd_genesi_tile { uchar expected_genesis_hash[ 32UL ]; ushort expected_shred_version; - ulong genesis_sz; - uchar genesis[ GENESIS_MAX_SZ ] __attribute__((aligned(alignof(fd_genesis_solana_global_t)))); /* 10 MiB buffer for decoded genesis */ - uchar buffer[ GENESIS_MAX_SZ ]; /* 10 MiB buffer for reading genesis file */ + uchar genesis[ FD_GENESIS_MAX_MESSAGE_SIZE ] __attribute__((aligned(alignof(fd_genesis_t)))); /* 10 MiB buffer for decoded genesis */ + uchar buffer[ FD_GENESIS_MAX_MESSAGE_SIZE ]; /* 10 MiB buffer for reading genesis file */ char genesis_path[ PATH_MAX ]; @@ -119,44 +116,38 @@ initialize_accdb( fd_genesi_tile_t * ctx ) { /* Insert accounts at root */ fd_funk_txn_xid_t root_xid; fd_funk_txn_xid_set_root( &root_xid ); - fd_genesis_solana_global_t * genesis = fd_type_pun( ctx->genesis ); - - fd_pubkey_account_pair_global_t const * accounts = fd_genesis_solana_accounts_join( genesis ); + fd_genesis_t * genesis = fd_type_pun( ctx->genesis ); fd_funk_t * funk = fd_accdb_user_v1_funk( ctx->accdb ); for( ulong i=0UL; iaccounts_len; i++ ) { - fd_pubkey_account_pair_global_t const * account = &accounts[ i ]; + fd_genesis_account_t * account = fd_type_pun( (uchar *)genesis + genesis->accounts_off[ i ] ); - /* FIXME use accdb API */ + /* FIXME: use accdb API */ fd_funk_rec_prepare_t prepare[1]; - fd_funk_rec_key_t key[1]; memcpy( key->uc, account->key.uc, sizeof(fd_pubkey_t) ); + fd_funk_rec_key_t key[1]; memcpy( key->uc, account->pubkey, sizeof(fd_pubkey_t) ); fd_funk_rec_t * rec = fd_funk_rec_prepare( funk, &root_xid, key, prepare, NULL ); FD_TEST( rec ); - fd_account_meta_t * meta = fd_funk_val_truncate( rec, funk->alloc, funk->wksp, 16UL, sizeof(fd_account_meta_t)+account->account.data_len, NULL ); + fd_account_meta_t * meta = fd_funk_val_truncate( rec, funk->alloc, funk->wksp, 16UL, sizeof(fd_account_meta_t)+account->meta.dlen, NULL ); FD_TEST( meta ); void * data = (void *)( meta+1 ); - fd_memcpy( meta->owner, account->account.owner.uc, sizeof(fd_pubkey_t) ); - meta->lamports = account->account.lamports; + fd_memcpy( meta->owner, account->meta.owner, sizeof(fd_pubkey_t) ); + meta->lamports = account->meta.lamports; meta->slot = 0UL; - meta->executable = !!account->account.executable; - meta->dlen = (uint)account->account.data_len; - fd_memcpy( data, fd_solana_account_data_join( &account->account ), account->account.data_len ); + meta->executable = !!account->meta.executable; + meta->dlen = (uint)account->meta.dlen; + fd_memcpy( data, account->data, account->meta.dlen ); fd_funk_rec_publish( funk, prepare ); fd_lthash_value_t new_hash[1]; - fd_hashes_account_lthash( &account->key, meta, data, new_hash ); + fd_hashes_account_lthash( fd_type_pun( account->pubkey ), meta, data, new_hash ); fd_lthash_add( ctx->lthash, new_hash ); } } static inline void -verify_cluster_type( fd_genesis_solana_global_t const * genesis, - uchar const * genesis_hash, - char const * genesis_path ) { -#define TESTNET (0) -#define MAINNET (1) -#define DEVNET (2) -#define DEVELOPMENT (3) +verify_cluster_type( fd_genesis_t const * genesis, + uchar const * genesis_hash, + char const * genesis_path ) { uchar mainnet_hash[ 32 ]; FD_TEST( fd_base58_decode_32( "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", mainnet_hash ) ); @@ -168,7 +159,7 @@ verify_cluster_type( fd_genesis_solana_global_t const * genesis, FD_TEST( fd_base58_decode_32( "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG", devnet_hash ) ); switch( genesis->cluster_type ) { - case MAINNET: { + case FD_GENESIS_TYPE_MAINNET: { if( FD_UNLIKELY( memcmp( genesis_hash, mainnet_hash, 32UL ) ) ) { FD_BASE58_ENCODE_32_BYTES( genesis_hash, genesis_hash_b58 ); FD_LOG_ERR(( "genesis file `%s` has cluster type MAINNET but unexpected genesis hash `%s`", @@ -176,7 +167,7 @@ verify_cluster_type( fd_genesis_solana_global_t const * genesis, } break; } - case TESTNET: { + case FD_GENESIS_TYPE_TESTNET: { if( FD_UNLIKELY( memcmp( genesis_hash, testnet_hash, 32UL ) ) ) { FD_BASE58_ENCODE_32_BYTES( genesis_hash, genesis_hash_b58 ); FD_LOG_ERR(( "genesis file `%s` has cluster type TESTNET but unexpected genesis hash `%s`", @@ -184,7 +175,7 @@ verify_cluster_type( fd_genesis_solana_global_t const * genesis, } break; } - case DEVNET: { + case FD_GENESIS_TYPE_DEVNET: { if( FD_UNLIKELY( memcmp( genesis_hash, devnet_hash, 32UL ) ) ) { FD_BASE58_ENCODE_32_BYTES( genesis_hash, genesis_hash_b58 ); FD_LOG_ERR(( "genesis file `%s` has cluster type DEVNET but unexpected genesis hash `%s`", @@ -195,11 +186,6 @@ verify_cluster_type( fd_genesis_solana_global_t const * genesis, default: break; } - -#undef TESTNET -#undef MAINNET -#undef DEVNET -#undef DEVELOPMENT } static void @@ -214,14 +200,16 @@ after_credit( fd_genesi_tile_t * ctx, if( FD_LIKELY( ctx->local_genesis ) ) { FD_TEST( -1!=ctx->in_fd ); + fd_genesis_t * genesis = fd_type_pun( ctx->genesis ); + uchar * dst = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk ); if( FD_UNLIKELY( ctx->bootstrap ) ) { fd_memcpy( dst, &ctx->lthash->bytes, sizeof(fd_lthash_value_t) ); fd_memcpy( dst+sizeof(fd_lthash_value_t), &ctx->genesis_hash, sizeof(fd_hash_t) ); - fd_memcpy( dst+sizeof(fd_lthash_value_t)+sizeof(fd_hash_t), ctx->genesis, ctx->genesis_sz ); + fd_memcpy( dst+sizeof(fd_lthash_value_t)+sizeof(fd_hash_t), ctx->genesis, genesis->total_sz ); fd_stem_publish( stem, 0UL, GENESI_SIG_BOOTSTRAP_COMPLETED, ctx->out_chunk, 0UL, 0UL, 0UL, 0UL ); - ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, ctx->genesis_sz+sizeof(fd_hash_t)+sizeof(fd_lthash_value_t), ctx->out_chunk0, ctx->out_wmark ); + ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, genesis->total_sz+sizeof(fd_hash_t)+sizeof(fd_lthash_value_t), ctx->out_chunk0, ctx->out_wmark ); } else { fd_memcpy( dst, ctx->genesis_hash, sizeof(fd_hash_t) ); fd_stem_publish( stem, 0UL, GENESI_SIG_GENESIS_HASH, ctx->out_chunk, sizeof(fd_hash_t), 0UL, 0UL, 0UL ); @@ -250,7 +238,7 @@ after_credit( fd_genesi_tile_t * ctx, int bzerr = BZ2_bzDecompressInit( &bzstrm, 0, 0 ); if( FD_UNLIKELY( BZ_OK!=bzerr ) ) FD_LOG_ERR(( "BZ2_bzDecompressInit() failed (%d)", bzerr )); - ulong decompressed_sz = GENESIS_MAX_SZ; + ulong decompressed_sz = FD_GENESIS_MAX_MESSAGE_SIZE; bzstrm.next_in = (char *)buffer; bzstrm.avail_in = (uint)buffer_sz; @@ -287,19 +275,12 @@ after_credit( fd_genesi_tile_t * ctx, } FD_TEST( !ctx->bootstrap ); + ulong size = fd_tar_meta_get_size( meta ); - fd_bincode_decode_ctx_t decode_ctx = { - .data = decompressed+512UL, - .dataend = decompressed+512UL+fd_tar_meta_get_size( meta ), - }; - - ctx->genesis_sz = 0UL; - int err = fd_genesis_solana_decode_footprint( &decode_ctx, &ctx->genesis_sz ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) FD_LOG_ERR(( "malformed genesis file from peer at http://" FD_IP4_ADDR_FMT ":%hu", FD_IP4_ADDR_FMT_ARGS( peer.addr ), peer.port )); - if( FD_UNLIKELY( ctx->genesis_sz>sizeof(ctx->genesis) ) ) FD_LOG_ERR(( "genesis file at `%s` decode footprint too large (%lu bytes, max %lu)", ctx->genesis_path, ctx->genesis_sz, sizeof(ctx->genesis) )); - - fd_genesis_solana_global_t * genesis = fd_genesis_solana_decode_global( ctx->genesis, &decode_ctx ); - FD_TEST( genesis ); + fd_genesis_t * genesis = fd_genesis_parse( ctx->genesis, decompressed+512UL, size ); + if( FD_UNLIKELY( !genesis ) ) { + FD_LOG_ERR(( "unable to decode downloaded solana genesis file due to violated hardcoded limits" )); + } verify_cluster_type( genesis, hash, ctx->genesis_path ); @@ -325,7 +306,7 @@ after_credit( fd_genesi_tile_t * ctx, char basename_partial[ PATH_MAX ]; FD_TEST( fd_cstr_printf_check( basename_partial, PATH_MAX, NULL, "%s.partial", basename ) ); - err = renameat2( ctx->out_dir_fd, basename_partial, ctx->out_dir_fd, basename, RENAME_NOREPLACE ); + int err = renameat2( ctx->out_dir_fd, basename_partial, ctx->out_dir_fd, basename, RENAME_NOREPLACE ); if( FD_UNLIKELY( -1==err ) ) FD_LOG_ERR(( "renameat2() failed (%i-%s)", errno, fd_io_strerror( errno ) )); FD_LOG_NOTICE(( "retrieved genesis `%s` from peer at http://" FD_IP4_ADDR_FMT ":%hu/genesis.tar.bz2", @@ -358,18 +339,10 @@ process_local_genesis( fd_genesi_tile_t * ctx, if( FD_UNLIKELY( -1==close( ctx->in_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) )); - fd_bincode_decode_ctx_t decode_ctx = { - .data = ctx->buffer, - .dataend = ctx->buffer+size, - }; - - ctx->genesis_sz = 0UL; - err = fd_genesis_solana_decode_footprint( &decode_ctx, &ctx->genesis_sz ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) FD_LOG_ERR(( "malformed genesis file at `%s`", genesis_path )); - if( FD_UNLIKELY( ctx->genesis_sz>sizeof(ctx->genesis) ) ) FD_LOG_ERR(( "genesis file at `%s` decode footprint too large (%lu bytes, max %lu)", genesis_path, ctx->genesis_sz, sizeof(ctx->genesis) )); - - fd_genesis_solana_global_t * genesis = fd_genesis_solana_decode_global( ctx->genesis, &decode_ctx ); - FD_TEST( genesis ); + fd_genesis_t * genesis = fd_genesis_parse( ctx->genesis, ctx->buffer, size ); + if( FD_UNLIKELY( !genesis ) ) { + FD_LOG_ERR(( "unable to decode solana genesis from local file due to violated hardcoded limits" )); + } union { uchar c[ 32 ]; diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c index d3120c849c..32718bcecc 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -28,6 +28,7 @@ #include "../../flamenco/runtime/fd_runtime.h" #include "../../flamenco/runtime/fd_runtime_stack.h" +#include "../../flamenco/runtime/fd_genesis_parse.h" #include "../../flamenco/fd_flamenco_base.h" #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h" @@ -1285,10 +1286,7 @@ boot_genesis( fd_replay_tile_t * ctx, uchar const * lthash = (uchar*)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ); uchar const * genesis_hash = (uchar*)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk )+sizeof(fd_lthash_value_t); - // TODO: Do not pass the fd_types type between tiles, it have offsets - // that are unsafe and can't be validated as being in-bounds. Need to - // pass an actual owned genesis type. - fd_genesis_solana_global_t const * genesis = fd_type_pun( (uchar*)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk )+sizeof(fd_hash_t)+sizeof(fd_lthash_value_t) ); + fd_genesis_t const * genesis = fd_type_pun( (uchar*)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk )+sizeof(fd_hash_t)+sizeof(fd_lthash_value_t) ); fd_bank_t * bank = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_IDX ); FD_TEST( bank ); diff --git a/src/flamenco/fd_flamenco_base.h b/src/flamenco/fd_flamenco_base.h index c4142c9946..1e057c8ba5 100644 --- a/src/flamenco/fd_flamenco_base.h +++ b/src/flamenco/fd_flamenco_base.h @@ -56,6 +56,9 @@ typedef struct fd_txn_out fd_txn_out_t; struct fd_log_collector; typedef struct fd_log_collector fd_log_collector_t; +struct fd_genesis; +typedef struct fd_genesis fd_genesis_t; + struct fd_account_meta { uchar owner[32]; ulong lamports; diff --git a/src/flamenco/genesis/fd_genesis_create.c b/src/flamenco/genesis/fd_genesis_create.c index f57309795e..19b46d5f0f 100644 --- a/src/flamenco/genesis/fd_genesis_create.c +++ b/src/flamenco/genesis/fd_genesis_create.c @@ -4,7 +4,6 @@ #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" diff --git a/src/flamenco/runtime/Local.mk b/src/flamenco/runtime/Local.mk index 5ed3ff78ba..2256336cfe 100644 --- a/src/flamenco/runtime/Local.mk +++ b/src/flamenco/runtime/Local.mk @@ -38,6 +38,9 @@ $(call add-objs,fd_compute_budget_details,fd_flamenco) $(call add-hdrs,fd_borrowed_account.h) $(call add-objs,fd_borrowed_account,fd_flamenco) +$(call add-hdrs,fd_genesis_parse.h) +$(call add-objs,fd_genesis_parse,fd_flamenco) + $(call add-hdrs,fd_txn_account.h) $(call add-objs,fd_txn_account,fd_flamenco) ifdef FD_HAS_INT128 @@ -85,5 +88,6 @@ ifdef FD_HAS_HOSTED #$(call make-unit-test,test_archive_block,test_archive_block, fd_flamenco fd_util fd_ballet,$(SECP256K1_LIBS)) # TODO: Flakes # $(call run-unit-test,test_txncache,) +$(call make-fuzz-test,fuzz_genesis_parse,fuzz_genesis_parse,fd_flamenco fd_ballet fd_util) endif endif diff --git a/src/flamenco/runtime/fd_genesis_parse.c b/src/flamenco/runtime/fd_genesis_parse.c new file mode 100644 index 0000000000..6d194ef072 --- /dev/null +++ b/src/flamenco/runtime/fd_genesis_parse.c @@ -0,0 +1,155 @@ +#include "fd_genesis_parse.h" +#include "fd_runtime_const.h" +#include "../../util/bits/fd_bits.h" + +/* Adapted from fd_txn_parse.c */ +#define CHECK_INIT( payload, payload_sz, offset ) \ + uchar const * _payload = (payload); \ + ulong const _payload_sz = (payload_sz); \ + ulong const _offset = (offset); \ + ulong _i = (offset); \ + (void) _payload; \ + (void) _offset; \ + +#define CHECK( cond ) do { \ + if( FD_UNLIKELY( !(cond) ) ) { \ + return 0; \ + } \ +} while( 0 ) + +#define CHECK_LEFT( n ) CHECK( (n)<=(_payload_sz-_i) ) + +#define INC( n ) (_i += (ulong)(n)) +#define CUR_OFFSET ((ushort)_i) +#define CURSOR (_payload+_i) + + +fd_genesis_t * +fd_genesis_parse( void * genesis_mem, + uchar const * bin, + ulong bin_sz ) { + FD_SCRATCH_ALLOC_INIT( l, genesis_mem ); + fd_genesis_t * genesis = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_genesis_t), sizeof(fd_genesis_t) ); + + CHECK_INIT( bin, bin_sz, 0U ); + + CHECK_LEFT( 8U ); genesis->creation_time = FD_LOAD( ulong, CURSOR ); INC( 8U ); + + CHECK_LEFT( 8U ); genesis->accounts_len = FD_LOAD( ulong, CURSOR ); INC( 8U ); + if( FD_UNLIKELY( genesis->accounts_len>FD_GENESIS_ACCOUNT_MAX_COUNT ) ) { + FD_LOG_WARNING(( "genesis accounts length %lu exceeds supported max count %lu", genesis->accounts_len, FD_GENESIS_ACCOUNT_MAX_COUNT )); + return NULL; + } + for( ulong i=0UL; iaccounts_len; i++ ) { + fd_genesis_account_t * account = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_genesis_account_t), sizeof(fd_genesis_account_t) ); + genesis->accounts_off[ i ] = (uint)((ulong)account-(ulong)genesis); + if( FD_UNLIKELY( genesis->accounts_off[ i ] + sizeof(fd_genesis_account_t) > FD_GENESIS_MAX_MESSAGE_SIZE ) ) { + FD_LOG_WARNING(( "genesis accounts offset %u exceeds supported max size %lu", genesis->accounts_off[ i ], FD_GENESIS_MAX_MESSAGE_SIZE )); + return NULL; + } + CHECK_LEFT( 32U ); fd_memcpy( account->pubkey, CURSOR, 32U ); INC( 32U ); + CHECK_LEFT( 8U ); account->meta.lamports = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); account->meta.dlen = (uint)FD_LOAD( ulong, CURSOR ); INC( 8U ); + if( FD_UNLIKELY( account->meta.dlen>FD_RUNTIME_ACC_SZ_MAX ) ) { + FD_LOG_WARNING(( "genesis builtin account data length %u exceeds supported max size %lu", account->meta.dlen, FD_RUNTIME_ACC_SZ_MAX )); + return NULL; + } + if( FD_UNLIKELY( genesis->accounts_off[ i ] + sizeof(fd_genesis_account_t) + account->meta.dlen > FD_GENESIS_MAX_MESSAGE_SIZE ) ) { + FD_LOG_WARNING(( "genesis builtin account data length %lu exceeds supported max size %lu", genesis->accounts_off[ i ] + sizeof(fd_genesis_account_t) + account->meta.dlen, FD_GENESIS_MAX_MESSAGE_SIZE )); + return NULL; + } + CHECK_LEFT( account->meta.dlen ); + uchar * data = FD_SCRATCH_ALLOC_APPEND( l, alignof(uchar), account->meta.dlen ); + fd_memcpy( data, CURSOR, account->meta.dlen ); INC( account->meta.dlen ); + CHECK_LEFT( 32U ); fd_memcpy( account->meta.owner, CURSOR, 32U ); INC( 32U ); + CHECK_LEFT( 1U ); account->meta.executable = FD_LOAD( uchar, CURSOR ); INC( 1U ); + CHECK_LEFT( 8U ); INC( 8U ); /* don't care about rent epoch */ + } + + CHECK_LEFT( 8U ); genesis->builtin_len = FD_LOAD( ulong, CURSOR ); INC( 8U ); + if( FD_UNLIKELY( genesis->builtin_len>FD_GENESIS_BUILTIN_MAX_COUNT ) ) { + FD_LOG_WARNING(( "genesis builtin length %lu exceeds supported max count %lu", genesis->builtin_len, FD_GENESIS_BUILTIN_MAX_COUNT )); + return NULL; + } + for( ulong i=0UL; ibuiltin_len; i++ ) { + fd_genesis_account_t * account = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_genesis_account_t), sizeof(fd_genesis_account_t) ); + genesis->builtin_off[ i ] = (uint)((ulong)account-(ulong)genesis); + if( FD_UNLIKELY( genesis->builtin_off[ i ] + sizeof(fd_genesis_account_t) > FD_GENESIS_MAX_MESSAGE_SIZE ) ) { + FD_LOG_WARNING(( "genesis builtin offset %lu exceeds supported max size %lu", genesis->builtin_off[ i ] + sizeof(fd_genesis_account_t), FD_GENESIS_MAX_MESSAGE_SIZE )); + return NULL; + } + CHECK_LEFT( 32U ); fd_memcpy( account->pubkey, CURSOR, 32U ); INC( 32U ); + CHECK_LEFT( 8U ); account->meta.lamports = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); account->meta.dlen = (uint)FD_LOAD( ulong, CURSOR ); INC( 8U ); + if( FD_UNLIKELY( account->meta.dlen>FD_RUNTIME_ACC_SZ_MAX ) ) { + FD_LOG_WARNING(( "genesis builtin account data length %u exceeds supported max size %lu", account->meta.dlen, FD_RUNTIME_ACC_SZ_MAX )); + return NULL; + } + if( FD_UNLIKELY( genesis->builtin_off[ i ] + sizeof(fd_genesis_account_t) + account->meta.dlen > FD_GENESIS_MAX_MESSAGE_SIZE ) ) { + FD_LOG_WARNING(( "genesis builtin account data length %lu exceeds supported max size %lu", genesis->builtin_off[ i ] + sizeof(fd_genesis_account_t) + account->meta.dlen, FD_GENESIS_MAX_MESSAGE_SIZE )); + return NULL; + } + CHECK_LEFT( account->meta.dlen ); + uchar * data = FD_SCRATCH_ALLOC_APPEND( l, alignof(uchar), account->meta.dlen ); + fd_memcpy( data, CURSOR, account->meta.dlen ); INC( account->meta.dlen ); + CHECK_LEFT( 32U ); fd_memcpy( account->meta.owner, CURSOR, 32U ); INC( 32U ); + CHECK_LEFT( 1U ); account->meta.executable = FD_LOAD( uchar, CURSOR ); INC( 1U ); + CHECK_LEFT( 8U ); INC( 8U ); /* don't care about rent epoch */ + } + + genesis->total_sz = (ulong)(FD_SCRATCH_ALLOC_FINI( l, alignof(fd_genesis_t) )) - (ulong)genesis_mem; + + CHECK_LEFT( 8U ); ulong rewards_len = FD_LOAD( ulong, CURSOR ); INC( 8U ); + for( ulong i=0UL; imeta.dlen ); INC( account->meta.dlen ); /* data */ + CHECK_LEFT( 32U ); INC( 32U ); /* owner */ + CHECK_LEFT( 1U ); INC( 1U ); /* executable */ + CHECK_LEFT( 8U ); INC( 8U ); /* rent epoch */ + } + + CHECK_LEFT( 8U ); genesis->poh.ticks_per_slot = FD_LOAD( ulong, CURSOR ); INC( 8U ); + + CHECK_LEFT( sizeof(ulong) ); INC( sizeof(ulong) ); /* unused */ + + CHECK_LEFT( 8U ); genesis->poh.tick_duration_secs = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 4U ); genesis->poh.tick_duration_ns = FD_LOAD( uint, CURSOR ); INC( 4U ); + CHECK_LEFT( 1U ); int has_target_tick_count = FD_LOAD( uchar, CURSOR ); INC( 1U ); + if( has_target_tick_count ) { CHECK_LEFT( 8U ); genesis->poh.target_tick_count = FD_LOAD( ulong, CURSOR ); INC( 8U ); } + else genesis->poh.target_tick_count = 0UL; + CHECK_LEFT( 1U ); int has_hashes_per_tick = FD_LOAD( uchar, CURSOR ); INC( 1U ); + if( has_hashes_per_tick ) { CHECK_LEFT( 8U ); genesis->poh.hashes_per_tick = FD_LOAD( ulong, CURSOR ); INC( 8U ); } + else genesis->poh.hashes_per_tick = 0UL; + + CHECK_LEFT( sizeof(ulong) ); INC( sizeof(ulong) ); /* bakcward compat v23 */ + + CHECK_LEFT( 8U ); genesis->fee_rate_governor.target_lamports_per_signature = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->fee_rate_governor.target_signatures_per_slot = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->fee_rate_governor.min_lamports_per_signature = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->fee_rate_governor.max_lamports_per_signature = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 1U ); genesis->fee_rate_governor.burn_percent = FD_LOAD( uchar, CURSOR ); INC( 1U ); + + CHECK_LEFT( 8U ); genesis->rent.lamports_per_uint8_year = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->rent.exemption_threshold = FD_LOAD( double, CURSOR ); INC( 8U ); + CHECK_LEFT( 1U ); genesis->rent.burn_percent = FD_LOAD( uchar, CURSOR ); INC( 1U ); + + CHECK_LEFT( 8U ); genesis->inflation.initial = FD_LOAD( double, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->inflation.terminal = FD_LOAD( double, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->inflation.taper = FD_LOAD( double, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->inflation.foundation = FD_LOAD( double, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->inflation.foundation_term = FD_LOAD( double, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); INC( 8U ); /* unused */ + + CHECK_LEFT( 8U ); genesis->epoch_schedule.slots_per_epoch = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->epoch_schedule.leader_schedule_slot_offset = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 1U ); genesis->epoch_schedule.warmup = FD_LOAD( uchar, CURSOR ); INC( 1U ); + CHECK_LEFT( 8U ); genesis->epoch_schedule.first_normal_epoch = FD_LOAD( ulong, CURSOR ); INC( 8U ); + CHECK_LEFT( 8U ); genesis->epoch_schedule.first_normal_slot = FD_LOAD( ulong, CURSOR ); INC( 8U ); + + CHECK_LEFT( 4U ); genesis->cluster_type = FD_LOAD( uint, CURSOR ); INC( 4U ); + + return genesis; +} diff --git a/src/flamenco/runtime/fd_genesis_parse.h b/src/flamenco/runtime/fd_genesis_parse.h new file mode 100644 index 0000000000..6c8399f256 --- /dev/null +++ b/src/flamenco/runtime/fd_genesis_parse.h @@ -0,0 +1,110 @@ +#ifndef HEADER_fd_src_flamenco_runtime_fd_genesis_parse_h +#define HEADER_fd_src_flamenco_runtime_fd_genesis_parse_h + +#include "../../util/fd_util_base.h" +#include "../fd_flamenco_base.h" + +/* These two constants are not defined at the Solana protocol level + and are instead used to bound out the amount of memory that will be + allocated for the genesis message. */ +#define FD_GENESIS_ACCOUNT_MAX_COUNT (2048UL) +#define FD_GENESIS_BUILTIN_MAX_COUNT (16UL) +#define FD_GENESIS_MAX_MESSAGE_SIZE (1024UL*1024UL*10UL) /* 10MiB */ + +#define FD_GENESIS_TYPE_TESTNET (0) +#define FD_GENESIS_TYPE_MAINNET (1) +#define FD_GENESIS_TYPE_DEVNET (2) +#define FD_GENESIS_TYPE_DEVELOPMENT (3) + +struct fd_genesis_account { + uchar pubkey[32]; + fd_account_meta_t meta; + uchar data[]; +}; +typedef struct fd_genesis_account fd_genesis_account_t; + +struct fd_genesis { + /* total_sz represents the total memory footprint taken up by + fd_genesis_t and the variable length data that follows. */ + ulong total_sz; + + ulong creation_time; + uint cluster_type; + + struct { + ulong ticks_per_slot; + ulong tick_duration_secs; + ulong tick_duration_ns; + ulong target_tick_count; + ulong hashes_per_tick; + } poh; + + struct { + ulong target_lamports_per_signature; + ulong target_signatures_per_slot; + ulong min_lamports_per_signature; + ulong max_lamports_per_signature; + uchar burn_percent; + } fee_rate_governor; + + struct { + ulong lamports_per_uint8_year; + double exemption_threshold; + uchar burn_percent; + } rent; + + struct { + double initial; + double terminal; + double taper; + double foundation; + double foundation_term; + } inflation; + + struct { + ulong slots_per_epoch; + ulong leader_schedule_slot_offset; + uchar warmup; + ulong first_normal_epoch; + ulong first_normal_slot; + } epoch_schedule; + + ulong accounts_len; + uint accounts_off[ FD_GENESIS_ACCOUNT_MAX_COUNT ]; + ulong builtin_len; + uint builtin_off[ FD_GENESIS_BUILTIN_MAX_COUNT ]; + + /* variable length account data follows */ +}; +typedef struct fd_genesis fd_genesis_t; + +FD_PROTOTYPES_BEGIN + +/* fd_genesis_parse is a bincode parser for an encoded genesis type + which outputs a pointer to a decoded struct (fd_genesis_t). + A genesis_mem which is a region of memory assumed to be at minimum + of footprint FD_GENESIS_MAX_MESSAGE_SIZE with alignment of + alignof(fd_genesis_t) is passed in along with a binary blob (bin) of + size (bin_sz). + + The data in the binary blob will be assumed to be a bincode encoded + genesis type and will be decoded into a fd_genesis_t + type using the memory in genesis_mem. If the type is a valid bincode + encoded genesis type and it doesn't violate any Solana cluster + protocol limits as well as any Firedancer specific buffer limits + (e.g. number of accounts, total accounts footprint, etc.) then a + pointer to a valid fd_genesis_t will be returned. If the decoding + fails due to being an invalid input or violating aforementioned + limits, then NULL will be returned. + + The bincode encoded type matches the Agave client GenesisConfig type. + https://github.com/anza-xyz/solana-sdk/blob/genesis-config%40v3.0.0/genesis-config/src/lib.rs#L59 */ + +fd_genesis_t * +fd_genesis_parse( void * genesis_mem, + uchar const * bin, + ulong bin_sz ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_runtime_fd_genesis_parse_h */ diff --git a/src/flamenco/runtime/fd_runtime.c b/src/flamenco/runtime/fd_runtime.c index 8816bbeb69..260ea4cc5a 100644 --- a/src/flamenco/runtime/fd_runtime.c +++ b/src/flamenco/runtime/fd_runtime.c @@ -7,6 +7,7 @@ #include "fd_hashes.h" #include "fd_runtime_err.h" #include "fd_runtime_stack.h" +#include "fd_genesis_parse.h" #include "fd_executor.h" #include "fd_txn_account.h" @@ -1346,28 +1347,41 @@ fd_runtime_genesis_init_program( fd_bank_t * bank, } static void -fd_runtime_init_bank_from_genesis( fd_banks_t * banks, - fd_bank_t * bank, - fd_funk_t * funk, - fd_funk_txn_xid_t const * xid, - fd_genesis_solana_global_t const * genesis_block, - fd_hash_t const * genesis_hash ) { +fd_runtime_init_bank_from_genesis( fd_banks_t * banks, + fd_bank_t * bank, + fd_funk_t * funk, + fd_funk_txn_xid_t const * xid, + fd_genesis_t const * genesis_block, + fd_hash_t const * genesis_hash ) { fd_bank_poh_set( bank, *genesis_hash ); fd_hash_t * bank_hash = fd_bank_bank_hash_modify( bank ); memset( bank_hash->hash, 0, FD_SHA256_HASH_SZ ); - fd_poh_config_global_t const * poh = &genesis_block->poh_config; - uint128 target_tick_duration = ((uint128)poh->target_tick_duration.seconds * 1000000000UL + (uint128)poh->target_tick_duration.nanoseconds); + uint128 target_tick_duration = (uint128)genesis_block->poh.tick_duration_secs * 1000000000UL + (uint128)genesis_block->poh.tick_duration_ns; - fd_bank_epoch_schedule_set( bank, genesis_block->epoch_schedule ); + fd_epoch_schedule_t * epoch_schedule = fd_bank_epoch_schedule_modify( bank ); + epoch_schedule->leader_schedule_slot_offset = genesis_block->epoch_schedule.leader_schedule_slot_offset; + epoch_schedule->warmup = genesis_block->epoch_schedule.warmup; + epoch_schedule->first_normal_epoch = genesis_block->epoch_schedule.first_normal_epoch; + epoch_schedule->first_normal_slot = genesis_block->epoch_schedule.first_normal_slot; + epoch_schedule->slots_per_epoch = genesis_block->epoch_schedule.slots_per_epoch; - fd_bank_rent_set( bank, genesis_block->rent ); + fd_rent_t * rent = fd_bank_rent_modify( bank ); + rent->lamports_per_uint8_year = genesis_block->rent.lamports_per_uint8_year; + rent->exemption_threshold = genesis_block->rent.exemption_threshold; + rent->burn_percent = genesis_block->rent.burn_percent; - fd_bank_block_height_set( bank, 0UL ); + fd_inflation_t * inflation = fd_bank_inflation_modify( bank ); + inflation->initial = genesis_block->inflation.initial; + inflation->terminal = genesis_block->inflation.terminal; + inflation->taper = genesis_block->inflation.taper; + inflation->foundation = genesis_block->inflation.foundation; + inflation->foundation_term = genesis_block->inflation.foundation_term; + inflation->unused = 0.0; - fd_bank_inflation_set( bank, genesis_block->inflation ); + fd_bank_block_height_set( bank, 0UL ); { /* FIXME Why is there a previous blockhash at genesis? Why is the @@ -1379,19 +1393,24 @@ fd_runtime_init_bank_from_genesis( fd_banks_t * banks, info->fee_calculator.lamports_per_signature = 0UL; } - fd_bank_fee_rate_governor_set( bank, genesis_block->fee_rate_governor ); + fd_fee_rate_governor_t * fee_rate_governor = fd_bank_fee_rate_governor_modify( bank ); + fee_rate_governor->target_lamports_per_signature = genesis_block->fee_rate_governor.target_lamports_per_signature; + fee_rate_governor->target_signatures_per_slot = genesis_block->fee_rate_governor.target_signatures_per_slot; + fee_rate_governor->min_lamports_per_signature = genesis_block->fee_rate_governor.min_lamports_per_signature; + fee_rate_governor->max_lamports_per_signature = genesis_block->fee_rate_governor.max_lamports_per_signature; + fee_rate_governor->burn_percent = genesis_block->fee_rate_governor.burn_percent; - fd_bank_max_tick_height_set( bank, genesis_block->ticks_per_slot * (fd_bank_slot_get( bank ) + 1) ); + fd_bank_max_tick_height_set( bank, genesis_block->poh.ticks_per_slot * (fd_bank_slot_get( bank ) + 1) ); - fd_bank_hashes_per_tick_set( bank, !!poh->hashes_per_tick ? poh->hashes_per_tick : 0UL ); + fd_bank_hashes_per_tick_set( bank, genesis_block->poh.hashes_per_tick ); - fd_bank_ns_per_slot_set( bank, (fd_w_u128_t) { .ud=target_tick_duration * genesis_block->ticks_per_slot } ); + fd_bank_ns_per_slot_set( bank, (fd_w_u128_t) { .ud=target_tick_duration * genesis_block->poh.ticks_per_slot } ); - fd_bank_ticks_per_slot_set( bank, genesis_block->ticks_per_slot ); + fd_bank_ticks_per_slot_set( bank, genesis_block->poh.ticks_per_slot ); fd_bank_genesis_creation_time_set( bank, genesis_block->creation_time ); - fd_bank_slots_per_year_set( bank, SECONDS_PER_YEAR * (1000000000.0 / (double)target_tick_duration) / (double)genesis_block->ticks_per_slot ); + fd_bank_slots_per_year_set( bank, SECONDS_PER_YEAR * (1000000000.0 / (double)target_tick_duration) / (double)genesis_block->poh.ticks_per_slot ); fd_bank_signature_count_set( bank, 0UL ); @@ -1409,30 +1428,30 @@ fd_runtime_init_bank_from_genesis( fd_banks_t * banks, ulong capitalization = 0UL; - fd_pubkey_account_pair_global_t const * accounts = fd_genesis_solana_accounts_join( genesis_block ); for( ulong i=0UL; iaccounts_len; i++ ) { - fd_pubkey_account_pair_global_t const * acc = &accounts[ i ]; - capitalization = fd_ulong_sat_add( capitalization, acc->account.lamports ); + fd_genesis_account_t * account = fd_type_pun( (uchar *)genesis_block + genesis_block->accounts_off[ i ] ); + + capitalization = fd_ulong_sat_add( capitalization, account->meta.lamports ); - uchar const * acc_data = fd_solana_account_data_join( &acc->account ); + uchar const * acc_data = account->data; - if( !memcmp( acc->account.owner.key, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) ) { + if( !memcmp( account->meta.owner, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) ) { /* This means that there is a vote account which should be inserted into the vote states. Even after the vote account is inserted, we still don't know the total amount of stake that is delegated to the vote account. This must be calculated later. */ - fd_vote_states_update_from_account( vote_states, &acc->key, acc_data, acc->account.data_len ); - } else if( !memcmp( acc->account.owner.key, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) ) { + fd_vote_states_update_from_account( vote_states, fd_type_pun( account->pubkey ), acc_data, account->meta.dlen ); + } else if( !memcmp( account->meta.owner, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) ) { /* If an account is a stake account, then it must be added to the stake delegations cache. We should only add stake accounts that have a valid non-zero stake. */ fd_stake_state_v2_t stake_state = {0}; if( FD_UNLIKELY( !fd_bincode_decode_static( stake_state_v2, &stake_state, - acc_data, acc->account.data_len, + acc_data, account->meta.dlen, NULL ) ) ) { - FD_BASE58_ENCODE_32_BYTES( acc->key.key, stake_b58 ); + FD_BASE58_ENCODE_32_BYTES( account->pubkey, stake_b58 ); FD_LOG_ERR(( "Failed to deserialize genesis stake account %s", stake_b58 )); } if( !fd_stake_state_v2_is_stake( &stake_state ) ) continue; @@ -1440,7 +1459,7 @@ fd_runtime_init_bank_from_genesis( fd_banks_t * banks, fd_stake_delegations_update( stake_delegations, - (fd_pubkey_t *)acc->key.key, + (fd_pubkey_t *)account->pubkey, &stake_state.inner.stake.stake.delegation.voter_pubkey, stake_state.inner.stake.stake.delegation.stake, stake_state.inner.stake.stake.delegation.activation_epoch, @@ -1448,7 +1467,7 @@ fd_runtime_init_bank_from_genesis( fd_banks_t * banks, stake_state.inner.stake.stake.credits_observed, stake_state.inner.stake.stake.delegation.warmup_cooldown_rate ); - } else if( !memcmp( acc->account.owner.key, fd_solana_feature_program_id.key, sizeof(fd_pubkey_t) ) ) { + } else if( !memcmp( account->meta.owner, fd_solana_feature_program_id.key, sizeof(fd_pubkey_t) ) ) { /* Feature Account */ /* Scan list of feature IDs to resolve address=>feature offset */ @@ -1456,7 +1475,7 @@ fd_runtime_init_bank_from_genesis( fd_banks_t * banks, for( fd_feature_id_t const * id = fd_feature_iter_init(); !fd_feature_iter_done( id ); id = fd_feature_iter_next( id ) ) { - if( !memcmp( acc->key.key, id->id.key, sizeof(fd_pubkey_t) ) ) { + if( !memcmp( account->pubkey, id->id.key, sizeof(fd_pubkey_t) ) ) { found = id; break; } @@ -1465,15 +1484,15 @@ fd_runtime_init_bank_from_genesis( fd_banks_t * banks, if( found ) { /* Load feature activation */ fd_feature_t feature[1]; - FD_TEST( fd_bincode_decode_static( feature, feature, acc_data, acc->account.data_len, NULL ) ); + FD_TEST( fd_bincode_decode_static( feature, feature, acc_data, account->meta.dlen, NULL ) ); fd_features_t * features = fd_bank_features_modify( bank ); if( feature->has_activated_at ) { - FD_BASE58_ENCODE_32_BYTES( acc->key.key, pubkey_b58 ); + FD_BASE58_ENCODE_32_BYTES( account->pubkey, pubkey_b58 ); FD_LOG_DEBUG(( "Feature %s activated at %lu (genesis)", pubkey_b58, feature->activated_at )); fd_features_set( features, found, feature->activated_at ); } else { - FD_BASE58_ENCODE_32_BYTES( acc->key.key, pubkey_b58 ); + FD_BASE58_ENCODE_32_BYTES( account->pubkey, pubkey_b58 ); FD_LOG_DEBUG(( "Feature %s not activated (genesis)", pubkey_b58 )); fd_features_set( features, found, ULONG_MAX ); } @@ -1512,10 +1531,10 @@ fd_runtime_init_bank_from_genesis( fd_banks_t * banks, vote_states = fd_bank_vote_states_locking_modify( bank ); for( ulong i=0UL; iaccounts_len; i++ ) { - fd_pubkey_account_pair_global_t const * acc = &accounts[ i ]; + fd_genesis_account_t * account = fd_type_pun( (uchar *)genesis_block + genesis_block->accounts_off[ i ] ); - if( !memcmp( acc->account.owner.key, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) ) { - fd_vote_state_ele_t * vote_state = fd_vote_states_query( vote_states, &acc->key ); + if( !memcmp( account->meta.owner, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) ) { + fd_vote_state_ele_t * vote_state = fd_vote_states_query( vote_states, fd_type_pun( account->pubkey ) ); vote_state->stake_t_1 = vote_state->stake; vote_state->stake_t_2 = vote_state->stake; @@ -1590,15 +1609,15 @@ fd_runtime_process_genesis_block( fd_bank_t * bank, } void -fd_runtime_read_genesis( fd_banks_t * banks, - fd_bank_t * bank, - fd_accdb_user_t * accdb, - fd_funk_txn_xid_t const * xid, - fd_capture_ctx_t * capture_ctx, - fd_hash_t const * genesis_hash, - fd_lthash_value_t const * genesis_lthash, - fd_genesis_solana_global_t const * genesis_block, - fd_runtime_stack_t * runtime_stack ) { +fd_runtime_read_genesis( fd_banks_t * banks, + fd_bank_t * bank, + fd_accdb_user_t * accdb, + fd_funk_txn_xid_t const * xid, + fd_capture_ctx_t * capture_ctx, + fd_hash_t const * genesis_hash, + fd_lthash_value_t const * genesis_lthash, + fd_genesis_t const * genesis_block, + fd_runtime_stack_t * runtime_stack ) { fd_lthash_value_t * lthash = fd_bank_lthash_locking_modify( bank ); *lthash = *genesis_lthash; @@ -1614,13 +1633,12 @@ fd_runtime_read_genesis( fd_banks_t * banks, /* Write the native programs to the accounts db. */ - fd_string_pubkey_pair_global_t * nips = fd_genesis_solana_native_instruction_processors_join( genesis_block ); - - for( ulong i=0UL; inative_instruction_processors_len; i++ ) { - fd_string_pubkey_pair_global_t const * a = &nips[ i ]; + for( ulong i=0UL; ibuiltin_len; i++ ) { + fd_genesis_account_t * account = fd_type_pun( (uchar *)genesis_block + genesis_block->builtin_off[ i ] ); - uchar const * string = fd_string_pubkey_pair_string_join( a ); - fd_write_builtin_account( bank, accdb, xid, capture_ctx, a->pubkey, (const char *)string, a->string_len ); + fd_pubkey_t pubkey; + fd_memcpy( pubkey.uc, account->pubkey, sizeof(fd_pubkey_t) ); + fd_write_builtin_account( bank, accdb, xid, capture_ctx, pubkey, (const char *)account->data, account->meta.dlen ); } fd_features_restore( bank, funk, xid ); diff --git a/src/flamenco/runtime/fd_runtime_helpers.h b/src/flamenco/runtime/fd_runtime_helpers.h index fb0e6af57b..3fd72ed456 100644 --- a/src/flamenco/runtime/fd_runtime_helpers.h +++ b/src/flamenco/runtime/fd_runtime_helpers.h @@ -80,15 +80,15 @@ fd_runtime_new_fee_rate_governor_derived( fd_bank_t * bank, ulong latest_signatures_per_slot ); void -fd_runtime_read_genesis( fd_banks_t * banks, - fd_bank_t * bank, - fd_accdb_user_t * accdb, - fd_funk_txn_xid_t const * xid, - fd_capture_ctx_t * capture_ctx, - fd_hash_t const * genesis_hash, - fd_lthash_value_t const * genesis_lthash, - fd_genesis_solana_global_t const * genesis_block, - fd_runtime_stack_t * runtime_stack ); +fd_runtime_read_genesis( fd_banks_t * banks, + fd_bank_t * bank, + fd_accdb_user_t * accdb, + fd_funk_txn_xid_t const * xid, + fd_capture_ctx_t * capture_ctx, + fd_hash_t const * genesis_hash, + fd_lthash_value_t const * genesis_lthash, + fd_genesis_t const * genesis_block, + fd_runtime_stack_t * runtime_stack ); /* Error logging handholding assertions */ diff --git a/src/flamenco/runtime/fuzz_genesis_parse.c b/src/flamenco/runtime/fuzz_genesis_parse.c new file mode 100644 index 0000000000..7434826dd0 --- /dev/null +++ b/src/flamenco/runtime/fuzz_genesis_parse.c @@ -0,0 +1,71 @@ +#include "fd_genesis_parse.h" +#include "fd_runtime_const.h" +#include +#include + +static uchar genesis_buf[ FD_GENESIS_MAX_MESSAGE_SIZE ] __attribute__((aligned(alignof(fd_genesis_t)))); + +int +LLVMFuzzerInitialize( int * argc, + char *** argv ) { + /* Set up shell without signal handlers */ + putenv( "FD_LOG_BACKTRACE=0" ); + fd_boot( argc, argv ); + fd_log_level_stderr_set(4); + atexit( fd_halt ); + return 0; +} + +static int +genesis_accounts_check( uchar genesis_buf[ static FD_GENESIS_MAX_MESSAGE_SIZE ] ) { + + fd_genesis_t * genesis = fd_type_pun( genesis_buf ); + ulong genesis_sz = genesis->total_sz; + ulong lowest_expected_offset = sizeof(fd_genesis_t); + + if( FD_UNLIKELY( genesis_sz>FD_GENESIS_MAX_MESSAGE_SIZE ) ) return 0; + + if( FD_UNLIKELY( genesis->accounts_len>FD_GENESIS_ACCOUNT_MAX_COUNT ) ) return 0; + for( ulong i=0UL; iaccounts_len; i++ ) { + ulong account_off = genesis->accounts_off[ i ]; + if( FD_UNLIKELY( account_off>genesis_sz ) ) return 0; + if( FD_UNLIKELY( account_offmeta.dlen>FD_RUNTIME_ACC_SZ_MAX ) ) return 0; + + ulong next_offset = account_off+sizeof(fd_genesis_account_t)+account->meta.dlen; + if( FD_UNLIKELY( next_offsetbuiltin_len>FD_GENESIS_BUILTIN_MAX_COUNT ) ) return 0; + for( ulong i=0UL; ibuiltin_len; i++ ) { + ulong builtin_off = genesis->builtin_off[ i ]; + if( FD_UNLIKELY( builtin_off>genesis_sz ) ) return 0; + if( FD_UNLIKELY( builtin_offmeta.dlen>FD_RUNTIME_ACC_SZ_MAX ) ) return 0; + + ulong next_offset = builtin_off+sizeof(fd_genesis_account_t)+builtin->meta.dlen; + if( FD_UNLIKELY( next_offsetgenesis_sz ) ) return 0; + return 1; +} + +int +LLVMFuzzerTestOneInput( uchar const * data, + ulong size ) { + + if( !fd_genesis_parse( genesis_buf, data, size ) ) return 0; + /* In the genesis, the only two fields that are not fixed size are the + accounts and the built-in accounts. The offsets and bounds of each + of the accounts are checked here. */ + assert( genesis_accounts_check( genesis_buf ) ); + + return 0; +}