From e99b1450698c7fad069a1b8527284e42f60a13ed Mon Sep 17 00:00:00 2001 From: Yufeng Zhou Date: Thu, 2 Jan 2025 19:59:54 +0000 Subject: [PATCH] flamenco: implement verify_ticks --- src/app/fdctl/run/tiles/fd_replay.c | 35 ++- src/app/ledger/main.c | 11 +- .../runtime/context/fd_exec_slot_ctx.h | 2 - src/flamenco/runtime/fd_blockstore.h | 7 +- src/flamenco/runtime/fd_runtime.c | 293 ++++++++++++------ src/flamenco/runtime/fd_runtime.h | 46 ++- 6 files changed, 279 insertions(+), 115 deletions(-) diff --git a/src/app/fdctl/run/tiles/fd_replay.c b/src/app/fdctl/run/tiles/fd_replay.c index 92c8922639..d6262a4dcb 100644 --- a/src/app/fdctl/run/tiles/fd_replay.c +++ b/src/app/fdctl/run/tiles/fd_replay.c @@ -998,7 +998,7 @@ prepare_new_block_execution( fd_replay_tile_ctx_t * ctx, FD_TEST( fork == child ); // fork is advancing - FD_LOG_NOTICE(( "new block execution - slowt: %lu, parent_slot: %lu", curr_slot, ctx->parent_slot )); + FD_LOG_NOTICE(( "new block execution - slot: %lu, parent_slot: %lu", curr_slot, ctx->parent_slot )); fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( fork->slot_ctx.epoch_ctx ); /* if it is an epoch boundary, push out stake weights */ @@ -1006,8 +1006,12 @@ prepare_new_block_execution( fd_replay_tile_ctx_t * ctx, is_new_epoch_in_new_block = (int)fd_runtime_is_epoch_boundary( epoch_bank, fork->slot_ctx.slot_bank.slot, fork->slot_ctx.slot_bank.prev_slot ); } - fork->slot_ctx.slot_bank.prev_slot = fork->slot_ctx.slot_bank.slot; - fork->slot_ctx.slot_bank.slot = curr_slot; + fork->slot_ctx.slot_bank.prev_slot = fork->slot_ctx.slot_bank.slot; + fork->slot_ctx.slot_bank.slot = curr_slot; + fork->slot_ctx.slot_bank.tick_height = fork->slot_ctx.slot_bank.max_tick_height; + if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS != fd_runtime_compute_max_tick_height( epoch_bank->ticks_per_slot, curr_slot, &fork->slot_ctx.slot_bank.max_tick_height ) ) ) { + FD_LOG_ERR(( "couldn't compute tick height/max tick height slot %lu ticks_per_slot %lu", curr_slot, epoch_bank->ticks_per_slot )); + } fork->slot_ctx.enable_exec_recording = ctx->tx_metadata_storage; if( fd_runtime_is_epoch_boundary( epoch_bank, fork->slot_ctx.slot_bank.slot, fork->slot_ctx.slot_bank.prev_slot ) ) { @@ -1025,7 +1029,9 @@ prepare_new_block_execution( fd_replay_tile_ctx_t * ctx, fd_exec_epoch_ctx_from_prev( epoch_fork->epoch_ctx, prev_epoch_ctx ); fork->slot_ctx.epoch_ctx = epoch_fork->epoch_ctx; } + fork->slot_ctx.status_cache = ctx->status_cache; + fd_funk_txn_xid_t xid = { 0 }; if( flags & REPLAY_FLAG_PACKED_MICROBLOCK ) { @@ -1039,6 +1045,25 @@ prepare_new_block_execution( fd_replay_tile_ctx_t * ctx, fork->slot_ctx.funk_txn = fd_funk_txn_prepare(ctx->funk, fork->slot_ctx.funk_txn, &xid, 1); fd_funk_end_write( ctx->funk ); + if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS != fd_runtime_block_pre_execute_process_new_epoch( &fork->slot_ctx ) ) ) { + FD_LOG_ERR(( "couldn't process new epoch" )); + } + + fd_blockstore_start_read( ctx->blockstore ); + fd_block_t * block = fd_blockstore_block_query( ctx->blockstore, curr_slot ); + fd_blockstore_end_read( ctx->blockstore ); + ulong tick_res = fd_runtime_block_verify_ticks( + fd_blockstore_block_micro_laddr( ctx->blockstore, block ), + block->micros_cnt, + fd_blockstore_block_data_laddr( ctx->blockstore, block ), + fork->slot_ctx.slot_bank.tick_height, + fork->slot_ctx.slot_bank.max_tick_height, + fork->slot_ctx.epoch_ctx->epoch_bank.hashes_per_tick + ); + if( FD_UNLIKELY( tick_res != FD_BLOCK_OK ) ) { + FD_LOG_WARNING(( "failed to verify ticks res %lu slot %lu prev_slot %lu", tick_res, curr_slot, fork->slot_ctx.slot_bank.prev_slot )); + } + int res = fd_runtime_block_execute_prepare( &fork->slot_ctx ); if( res != FD_RUNTIME_EXECUTE_SUCCESS ) { FD_LOG_ERR(( "block prep execute failed" )); @@ -1250,10 +1275,6 @@ after_frag( fd_replay_tile_ctx_t * ctx, fd_blockstore_end_read( ctx->blockstore ); fork->slot_ctx.block = block_; - /* TODO:FIXME: This needs to be unhacked. */ - fork->slot_ctx.slot_bank.max_tick_height += 64UL * (curr_slot - ctx->parent_slot); - fork->slot_ctx.slot_bank.tick_height += 64UL * (curr_slot - ctx->parent_slot); - int res = fd_runtime_block_execute_finalize_tpool( &fork->slot_ctx, ctx->capture_ctx, block_info, ctx->tpool ); if( res != FD_RUNTIME_EXECUTE_SUCCESS ) { diff --git a/src/app/ledger/main.c b/src/app/ledger/main.c index 2db873db70..bda277e267 100644 --- a/src/app/ledger/main.c +++ b/src/app/ledger/main.c @@ -383,16 +383,15 @@ runtime_replay( fd_ledger_args_t * ledger_args ) { fd_blockstore_end_read( blockstore ); if( blk == NULL ) { FD_LOG_WARNING(( "failed to read slot %lu", slot )); - /* TODO: This is currently a hack because ticks are not correctly - computed or handled in the runtime. It is neceesary to update ticks - for skipped slots for snapshot creation. */ - ledger_args->slot_ctx->slot_bank.tick_height += 64UL; - ledger_args->slot_ctx->slot_bank.max_tick_height += 64UL; - continue; } ledger_args->slot_ctx->block = blk; + ledger_args->slot_ctx->slot_bank.tick_height = ledger_args->slot_ctx->slot_bank.max_tick_height; + if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS != fd_runtime_compute_max_tick_height( ledger_args->epoch_ctx->epoch_bank.ticks_per_slot, slot, &ledger_args->slot_ctx->slot_bank.max_tick_height ) ) ) { + FD_LOG_ERR(( "couldn't compute max tick height slot %lu ticks_per_slot %lu", slot, ledger_args->epoch_ctx->epoch_bank.ticks_per_slot )); + } + if( ledger_args->slot_ctx->root_slot%ledger_args->snapshot_freq==0UL && !ledger_args->is_snapshotting ) { ledger_args->is_snapshotting = 1; diff --git a/src/flamenco/runtime/context/fd_exec_slot_ctx.h b/src/flamenco/runtime/context/fd_exec_slot_ctx.h index 290db7757d..2153f9795b 100644 --- a/src/flamenco/runtime/context/fd_exec_slot_ctx.h +++ b/src/flamenco/runtime/context/fd_exec_slot_ctx.h @@ -81,8 +81,6 @@ struct __attribute__((aligned(8UL))) fd_exec_slot_ctx { fd_txncache_t * status_cache; fd_slot_history_t slot_history[1]; - ulong tick_count; - int enable_exec_recording; /* Enable/disable execution metadata recording, e.g. txn logs. Analogue of Agave's ExecutionRecordingConfig. */ diff --git a/src/flamenco/runtime/fd_blockstore.h b/src/flamenco/runtime/fd_blockstore.h index a4a78c4569..a5c1987448 100644 --- a/src/flamenco/runtime/fd_blockstore.h +++ b/src/flamenco/runtime/fd_blockstore.h @@ -232,7 +232,7 @@ struct fd_block { ulong shreds_cnt; ulong batch_gaddr; /* list of fd_block_entry_batch_t */ ulong batch_cnt; - ulong micros_gaddr; /* ptr to the list of fd_blockstore_micro_t */ + ulong micros_gaddr; /* ptr to the list of fd_block_micro_t */ ulong micros_cnt; ulong txns_gaddr; /* ptr to the list of fd_block_txn_t */ ulong txns_cnt; @@ -572,6 +572,11 @@ fd_blockstore_block_batch_laddr( fd_blockstore_t * blockstore, fd_block_t * bloc return fd_wksp_laddr_fast( fd_blockstore_wksp( blockstore ), block->batch_gaddr ); } +FD_FN_PURE static inline fd_block_micro_t * +fd_blockstore_block_micro_laddr( fd_blockstore_t * blockstore, fd_block_t * block ) { + return fd_wksp_laddr_fast( fd_blockstore_wksp( blockstore ), block->micros_gaddr ); +} + /* fd_buf_shred_query queries the blockstore for shred at slot, shred_idx. Returns a pointer to the shred or NULL if not in blockstore. The returned pointer lifetime is until the shred is diff --git a/src/flamenco/runtime/fd_runtime.c b/src/flamenco/runtime/fd_runtime.c index 7db194b053..feaa1da14f 100644 --- a/src/flamenco/runtime/fd_runtime.c +++ b/src/flamenco/runtime/fd_runtime.c @@ -65,6 +65,31 @@ /* Public Runtime Helpers */ /******************************************************************************/ +/* + https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/bank.rs#L1254-L1258 + https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/bank.rs#L1749 + */ +int +fd_runtime_compute_max_tick_height( ulong ticks_per_slot, + ulong slot, + ulong * out_max_tick_height /* out */ ) { + ulong max_tick_height = 0UL; + if( FD_LIKELY( ticks_per_slot > 0UL ) ) { + ulong next_slot = fd_ulong_sat_add( slot, 1UL ); + if( FD_UNLIKELY( next_slot == slot ) ) { + FD_LOG_WARNING(( "max tick height addition overflowed slot %lu ticks_per_slot %lu", slot, ticks_per_slot )); + return FD_RUNTIME_EXECUTE_GENERIC_ERR; + } + if( FD_UNLIKELY( ULONG_MAX / ticks_per_slot < next_slot ) ) { + FD_LOG_WARNING(( "max tick height multiplication overflowed slot %lu ticks_per_slot %lu", slot, ticks_per_slot )); + return FD_RUNTIME_EXECUTE_GENERIC_ERR; + } + max_tick_height = fd_ulong_sat_mul( next_slot, ticks_per_slot ); + } + *out_max_tick_height = max_tick_height; + return FD_RUNTIME_EXECUTE_SUCCESS; +} + void fd_runtime_update_leaders( fd_exec_slot_ctx_t * slot_ctx, ulong slot ) { FD_SCRATCH_SCOPE_BEGIN { @@ -1054,37 +1079,91 @@ fd_runtime_block_sysvar_update_pre_execute( fd_exec_slot_ctx_t * slot_ctx ) { return 0; } -int -fd_runtime_block_execute_prepare( fd_exec_slot_ctx_t * slot_ctx ) { - /* Update block height */ - slot_ctx->slot_bank.block_height += 1UL; - fd_blockstore_start_write( slot_ctx->blockstore ); - fd_blockstore_block_height_update( slot_ctx->blockstore, - slot_ctx->slot_bank.slot, - slot_ctx->slot_bank.block_height ); +/* + TODO Perhaps update this function to support verifying ticks before + the block is full. Could be useful when we move to a streaming + execution model. + */ +ulong +fd_runtime_block_verify_ticks( fd_block_micro_t const * micro, + ulong micro_cnt, + uchar const * block_data, + ulong tick_height, + ulong max_tick_height, + ulong hashes_per_tick ) { + ulong tick_count = 0UL; + ulong tick_hash_count = 0UL; + uchar has_trailing_entry = 0U; + uchar invalid_tick_hash_count = 0U; + /* + Iterate over microblocks/entries to + (1) count the number of ticks + (2) check whether the last entry is a tick + (3) check whether ticks align with hashes per tick + + This precomputes everything we need in a single loop over the array. + In order to mimic the order of checks in Agave, + we cache the results of some checks but do not immediately return + an error. + */ + for( ulong i = 0UL; i < micro_cnt; i++ ) { + fd_microblock_hdr_t const * hdr = fd_type_pun_const( ( block_data + micro[ i ].off ) ); + tick_hash_count = fd_ulong_sat_add( tick_hash_count, hdr->hash_cnt ); + if( hdr->txn_cnt == 0UL ) { + tick_count++; + if( FD_LIKELY( hashes_per_tick > 1UL ) ) { + if( FD_UNLIKELY( tick_hash_count != hashes_per_tick ) ) { + FD_LOG_WARNING(( "tick_hash_count %lu hashes_per_tick %lu tick_count %lu i %lu micro_cnt %lu", tick_hash_count, hashes_per_tick, tick_count, i, micro_cnt )); + invalid_tick_hash_count = 1U; + } + } + tick_hash_count = 0UL; + continue; + } + /* This wasn't a tick entry, but it's the last entry. */ + if( FD_UNLIKELY( i == micro_cnt - 1UL ) ) { + FD_LOG_WARNING(( "entry %lu has %lu transactions expects 0", i, hdr->txn_cnt )); + has_trailing_entry = 1U; + } + } - // TODO: this is not part of block execution, move it. - if( slot_ctx->slot_bank.slot != 0UL ) { - slot_ctx->block = fd_blockstore_block_query( slot_ctx->blockstore, slot_ctx->slot_bank.slot ); - fd_blockstore_end_write( slot_ctx->blockstore ); + ulong next_tick_height = tick_height + tick_count; + if( FD_UNLIKELY( next_tick_height > max_tick_height ) ) { + FD_LOG_WARNING(( "Too many ticks tick_height %lu max_tick_height %lu hashes_per_tick %lu tick_count %lu", tick_height, max_tick_height, hashes_per_tick, tick_count )); + FD_LOG_WARNING(( "Too many ticks" )); + return FD_BLOCK_ERR_TOO_MANY_TICKS; + } + if( FD_UNLIKELY( next_tick_height < max_tick_height ) ) { + FD_LOG_WARNING(( "Too few ticks" )); + return FD_BLOCK_ERR_TOO_FEW_TICKS; + } + if( FD_UNLIKELY( has_trailing_entry ) ) { + FD_LOG_WARNING(( "Did not end with a tick" )); + return FD_BLOCK_ERR_TRAILING_ENTRY; + } - ulong slot_idx; - fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx ); - ulong prev_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.prev_slot, &slot_idx ); - ulong new_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.slot, &slot_idx ); - if( FD_UNLIKELY( slot_idx==1UL && new_epoch==0UL ) ) { - /* the block after genesis has a height of 1*/ - slot_ctx->slot_bank.block_height = 1UL; - } + /* Not returning FD_BLOCK_ERR_INVALID_LAST_TICK because we assume the + slot is full. */ - if( FD_UNLIKELY( prev_epochacc_mgr->funk ); - fd_process_new_epoch( slot_ctx, new_epoch - 1UL ); - fd_funk_end_write( slot_ctx->acc_mgr->funk ); + /* Don't care about low power hashing or no hashing. */ + if( FD_LIKELY( hashes_per_tick > 1UL ) ) { + if( FD_UNLIKELY( invalid_tick_hash_count ) ) { + FD_LOG_WARNING(( "Tick with invalid number of hashes found" )); + return FD_BLOCK_ERR_INVALID_TICK_HASH_COUNT; } - } else { + } + + return FD_BLOCK_OK; +} + +int +fd_runtime_block_execute_prepare( fd_exec_slot_ctx_t * slot_ctx ) { + if( slot_ctx->slot_bank.slot != 0UL ) { + fd_blockstore_start_write( slot_ctx->blockstore ); + fd_blockstore_block_height_update( slot_ctx->blockstore, + slot_ctx->slot_bank.slot, + slot_ctx->slot_bank.block_height ); + slot_ctx->block = fd_blockstore_block_query( slot_ctx->blockstore, slot_ctx->slot_bank.slot ); fd_blockstore_end_write( slot_ctx->blockstore ); } @@ -1098,14 +1177,6 @@ fd_runtime_block_execute_prepare( fd_exec_slot_ctx_t * slot_ctx ) { slot_ctx->nonvote_failed_txn_count = 0UL; slot_ctx->total_compute_units_used = 0UL; - if( slot_ctx->slot_bank.slot != 0UL && ( - FD_FEATURE_ACTIVE( slot_ctx, enable_partitioned_epoch_reward ) || - FD_FEATURE_ACTIVE( slot_ctx, partitioned_epoch_rewards_superfeature ) ) ) { - fd_funk_start_write( slot_ctx->acc_mgr->funk ); - fd_distribute_partitioned_epoch_rewards( slot_ctx ); - fd_funk_end_write( slot_ctx->acc_mgr->funk ); - } - fd_funk_start_write( slot_ctx->acc_mgr->funk ); int result = fd_runtime_block_sysvar_update_pre_execute( slot_ctx ); fd_funk_end_write( slot_ctx->acc_mgr->funk ); @@ -1564,8 +1635,7 @@ fd_runtime_execute_txn_task( void * tpool, pre-transactions checks in the v2.0.x Agave client from upstream to downstream is as follows: - confirm_slot_entries() which calls verify_ticks() - (which is currently unimplemented in firedancer) and + confirm_slot_entries() which calls verify_ticks() and verify_transaction(). verify_transaction() calls verify_and_hash_message() and verify_precompiles() which parallels fd_executor_txn_verify() and fd_executor_verify_precompiles(). @@ -2711,9 +2781,9 @@ fd_runtime_is_epoch_boundary( fd_epoch_bank_t * epoch_bank, ulong curr_slot, ulo slot_ctx->slot_bank.epoch_stakes holds the stakes at T-2 */ /* process for the start of a new epoch */ - -void fd_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx, - ulong parent_epoch ) { +static +void fd_runtime_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx, + ulong parent_epoch ) { FD_LOG_NOTICE(( "fd_process_new_epoch start" )); ulong slot; @@ -3237,28 +3307,6 @@ fd_runtime_block_collect_txns( fd_block_info_t const * block_info, return block_info->txn_cnt; } -/* TODO: Move this around once the changes to it are updated. */ - -int -fd_runtime_block_verify_ticks( fd_block_info_t const * block_info, - ulong tick_height, - ulong max_tick_height ) { - (void)tick_height; (void)max_tick_height; - ulong tick_count = 0UL; - for( ulong i = 0UL; i < block_info->microblock_batch_cnt; i++ ) { - fd_microblock_batch_info_t const * microblock_batch_info = &block_info->microblock_batch_infos[ i ]; - for( ulong j = 0UL; j < microblock_batch_info->microblock_cnt; j++ ) { - fd_microblock_info_t const * microblock_info = µblock_batch_info->microblock_infos[ i ]; - if( microblock_info->microblock_hdr.txn_cnt == 0UL ) { - /* if this mblk is a tick */ - tick_count++; - } - } - } - (void)tick_count; - return 0; -} - /******************************************************************************/ /* Genesis */ /*******************************************************************************/ @@ -3914,8 +3962,6 @@ fd_runtime_block_execute_tpool( fd_exec_slot_ctx_t * slot_ctx, fd_solcap_writer_set_slot( capture_ctx->capture, slot_ctx->slot_bank.slot ); } - slot_ctx->tick_count = 0UL; - long block_execute_time = -fd_log_wallclock(); int res = fd_runtime_block_execute_prepare( slot_ctx ); @@ -3926,15 +3972,8 @@ fd_runtime_block_execute_tpool( fd_exec_slot_ctx_t * slot_ctx, ulong txn_cnt = block_info->txn_cnt; fd_txn_p_t * txn_ptrs = fd_scratch_alloc( alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) ); - /* This now collects the tick entries in a block. */ fd_runtime_block_collect_txns( block_info, txn_ptrs ); - /* TODO: Currently the tick height is manually updated for each executed - slot as a hack to support snapshot loading. This code should be removed - once correct tick calculation is implemented. */ - slot_ctx->slot_bank.tick_height += 64UL; - slot_ctx->slot_bank.max_tick_height += 64UL; - res = fd_runtime_process_txns_in_waves_tpool( slot_ctx, capture_ctx, txn_ptrs, txn_cnt, tpool, spads, spad_cnt ); if( res != FD_RUNTIME_EXECUTE_SUCCESS ) { return res; @@ -3961,6 +4000,41 @@ fd_runtime_block_execute_tpool( fd_exec_slot_ctx_t * slot_ctx, } FD_SCRATCH_SCOPE_END; } +int +fd_runtime_block_pre_execute_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx ) { + /* Update block height. */ + slot_ctx->slot_bank.block_height += 1UL; + + if( slot_ctx->slot_bank.slot != 0UL ) { + ulong slot_idx; + fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx ); + ulong prev_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.prev_slot, &slot_idx ); + ulong new_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.slot, &slot_idx ); + if( FD_UNLIKELY( slot_idx==1UL && new_epoch==0UL ) ) { + /* The block after genesis has a height of 1. */ + slot_ctx->slot_bank.block_height = 1UL; + } + + if( FD_UNLIKELY( prev_epochacc_mgr->funk ); + fd_runtime_process_new_epoch( slot_ctx, new_epoch - 1UL ); + fd_funk_end_write( slot_ctx->acc_mgr->funk ); + } + } + + if( slot_ctx->slot_bank.slot != 0UL && ( + FD_FEATURE_ACTIVE( slot_ctx, enable_partitioned_epoch_reward ) || + FD_FEATURE_ACTIVE( slot_ctx, partitioned_epoch_rewards_superfeature ) ) ) { + fd_funk_start_write( slot_ctx->acc_mgr->funk ); + fd_distribute_partitioned_epoch_rewards( slot_ctx ); + fd_funk_end_write( slot_ctx->acc_mgr->funk ); + } + + return FD_RUNTIME_EXECUTE_SUCCESS; +} + int fd_runtime_block_eval_tpool( fd_exec_slot_ctx_t * slot_ctx, fd_capture_ctx_t * capture_ctx, @@ -3984,40 +4058,67 @@ fd_runtime_block_eval_tpool( fd_exec_slot_ctx_t * slot_ctx, long block_eval_time = -fd_log_wallclock(); fd_block_info_t block_info; - int ret = fd_runtime_block_prepare( slot_ctx->blockstore, slot_ctx->block, slot, fd_scratch_virtual(), &block_info ); - *txn_cnt = block_info.txn_cnt; + int ret = FD_RUNTIME_EXECUTE_SUCCESS; + do { + if( FD_UNLIKELY( (ret = fd_runtime_block_prepare( slot_ctx->blockstore, slot_ctx->block, slot, fd_scratch_virtual(), &block_info )) != FD_RUNTIME_EXECUTE_SUCCESS ) ) { + break; + } + *txn_cnt = block_info.txn_cnt; - /* Use the blockhash as the funk xid */ - fd_funk_txn_xid_t xid; + /* Use the blockhash as the funk xid */ + fd_funk_txn_xid_t xid; - fd_blockstore_start_read( slot_ctx->blockstore ); - fd_hash_t const * hash = fd_blockstore_block_hash_query( slot_ctx->blockstore, slot ); - if( FD_UNLIKELY( !hash ) ) { - ret = FD_RUNTIME_EXECUTE_GENERIC_ERR; - FD_LOG_WARNING(( "missing blockhash for %lu", slot )); - } else { - fd_memcpy( xid.uc, hash->uc, sizeof(fd_funk_txn_xid_t) ); - xid.ul[0] = slot_ctx->slot_bank.slot; - /* push a new transaction on the stack */ - fd_funk_start_write( funk ); - slot_ctx->funk_txn = fd_funk_txn_prepare( funk, slot_ctx->funk_txn, &xid, 1 ); - fd_funk_end_write( funk ); - } - fd_blockstore_end_read( slot_ctx->blockstore ); + fd_blockstore_start_read( slot_ctx->blockstore ); + fd_hash_t const * hash = fd_blockstore_block_hash_query( slot_ctx->blockstore, slot ); + if( FD_UNLIKELY( !hash ) ) { + ret = FD_RUNTIME_EXECUTE_GENERIC_ERR; + FD_LOG_WARNING(( "missing blockhash for %lu", slot )); + break; + } else { + fd_memcpy( xid.uc, hash->uc, sizeof(fd_funk_txn_xid_t) ); + xid.ul[0] = slot_ctx->slot_bank.slot; + /* push a new transaction on the stack */ + fd_funk_start_write( funk ); + slot_ctx->funk_txn = fd_funk_txn_prepare( funk, slot_ctx->funk_txn, &xid, 1 ); + fd_funk_end_write( funk ); + } + fd_blockstore_end_read( slot_ctx->blockstore ); - if( FD_RUNTIME_EXECUTE_SUCCESS == ret ) { - ret = fd_runtime_block_verify_tpool( &block_info, &slot_ctx->slot_bank.poh, &slot_ctx->slot_bank.poh, fd_scratch_virtual(), tpool ); - } - if( FD_RUNTIME_EXECUTE_SUCCESS == ret ) { - ret = fd_runtime_block_execute_tpool( slot_ctx, capture_ctx, &block_info, tpool, spads, spad_cnt ); - } + if( FD_UNLIKELY( (ret = fd_runtime_block_pre_execute_process_new_epoch( slot_ctx )) != FD_RUNTIME_EXECUTE_SUCCESS ) ) { + break; + } + + fd_blockstore_start_read( slot_ctx->blockstore ); + fd_block_t * block = fd_blockstore_block_query( slot_ctx->blockstore, slot ); + fd_blockstore_end_read( slot_ctx->blockstore ); + ulong tick_res = fd_runtime_block_verify_ticks( + fd_blockstore_block_micro_laddr( slot_ctx->blockstore, block ), + block->micros_cnt, + fd_blockstore_block_data_laddr( slot_ctx->blockstore, block ), + slot_ctx->slot_bank.tick_height, + slot_ctx->slot_bank.max_tick_height, + slot_ctx->epoch_ctx->epoch_bank.hashes_per_tick + ); + if( FD_UNLIKELY( tick_res != FD_BLOCK_OK ) ) { + FD_LOG_WARNING(( "failed to verify ticks res %lu slot %lu", tick_res, slot )); + ret = FD_RUNTIME_EXECUTE_GENERIC_ERR; + break; + } + + if( FD_UNLIKELY( (ret = fd_runtime_block_verify_tpool( &block_info, &slot_ctx->slot_bank.poh, &slot_ctx->slot_bank.poh, fd_scratch_virtual(), tpool )) != FD_RUNTIME_EXECUTE_SUCCESS ) ) { + break; + } + if( FD_UNLIKELY( (ret = fd_runtime_block_execute_tpool( slot_ctx, capture_ctx, &block_info, tpool, spads, spad_cnt )) != FD_RUNTIME_EXECUTE_SUCCESS ) ) { + break; + } + } while( 0 ); // FIXME: better way of using starting slot if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS != ret ) ) { FD_LOG_WARNING(( "execution failure, code %d", ret )); /* Skip over slot next time */ slot_ctx->slot_bank.slot = slot+1UL; - return 0; + return ret; } block_eval_time += fd_log_wallclock(); diff --git a/src/flamenco/runtime/fd_runtime.h b/src/flamenco/runtime/fd_runtime.h index 202ec0a798..a1bbaeb413 100644 --- a/src/flamenco/runtime/fd_runtime.h +++ b/src/flamenco/runtime/fd_runtime.h @@ -266,6 +266,15 @@ FD_PROTOTYPES_BEGIN /* Runtime Helpers ************************************************************/ +/* + Returns 0 on success, and non zero otherwise. On failure, the + out values will not be modified. + */ +int +fd_runtime_compute_max_tick_height( ulong ticks_per_slot, + ulong slot, + ulong * out_max_tick_height /* out */ ); + void fd_runtime_update_leaders( fd_exec_slot_ctx_t * slot_ctx, ulong slot ); @@ -282,6 +291,30 @@ fd_runtime_collect_rent_from_account( fd_exec_slot_ctx_t const * slot_ctx, /* Block Level Execution Prep/Finalize ****************************************/ +#define FD_BLOCK_OK (0UL) +#define FD_BLOCK_ERR_INCOMPLETE (1UL) +#define FD_BLOCK_ERR_INVALID_ENTRY_HASH (2UL) +#define FD_BLOCK_ERR_INVALID_LAST_TICK (3UL) +#define FD_BLOCK_ERR_TOO_FEW_TICKS (4UL) +#define FD_BLOCK_ERR_TOO_MANY_TICKS (5UL) +#define FD_BLOCK_ERR_INVALID_TICK_HASH_COUNT (6UL) +#define FD_BLOCK_ERR_TRAILING_ENTRY (7UL) +#define FD_BLOCK_ERR_DUPLICATE_BLOCK (8UL) + +/* + https://github.com/anza-xyz/agave/blob/v2.1.0/ledger/src/blockstore_processor.rs#L1096 + This function assumes a full block. + This needs to be called after epoch processing to get the up to date + hashes_per_tick. + */ +ulong +fd_runtime_block_verify_ticks( fd_block_micro_t const * micro, + ulong micro_cnt, + uchar const * block_data, + ulong tick_height, + ulong max_tick_height, + ulong hashes_per_tick ); + int fd_runtime_block_execute_prepare( fd_exec_slot_ctx_t * slot_ctx ); @@ -344,9 +377,16 @@ fd_runtime_is_epoch_boundary( fd_epoch_bank_t * epoch_bank, ulong curr_slot, ulong prev_slot ); -void -fd_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx, - ulong parent_epoch ); +/* + This is roughly Agave's process_new_epoch() which gets called from + new_from_parent() for every slot. + https://github.com/anza-xyz/agave/blob/v1.18.26/runtime/src/bank.rs#L1483 + This needs to be called after funk_txn_prepare() because the accounts + that we modify when processing a new epoch need to be hashed into + the bank hash. + */ +int +fd_runtime_block_pre_execute_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx ); /* Debugging Tools ************************************************************/