From 8a42930fd6d4561d0e4eef0614a2c6ae111ca97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Guillemet?= Date: Thu, 6 Nov 2025 22:37:04 +0100 Subject: [PATCH 1/3] fix_expiration_logic --- src/ballet/txn/fd_txn.h | 4 +++ src/disco/bundle/fd_bundle_client.c | 24 ++++++++--------- src/disco/fd_txn_m.h | 8 +++--- src/disco/fd_txn_p.h | 2 +- src/disco/pack/fd_pack.c | 2 +- src/disco/pack/fd_pack_tile.c | 41 ++++++++++++----------------- src/disco/tiles.h | 6 +++-- src/discof/replay/fd_replay_tile.c | 1 + src/discof/replay/fd_replay_tile.h | 1 + src/discof/resolv/fd_resolv_tile.c | 28 +++++++++++--------- src/discoh/poh/fd_poh_tile.c | 5 +++- src/discoh/resolv/fd_resolv_tile.c | 34 +++++++++++++----------- 12 files changed, 83 insertions(+), 73 deletions(-) diff --git a/src/ballet/txn/fd_txn.h b/src/ballet/txn/fd_txn.h index a8e9d40e580..66df65f94b5 100644 --- a/src/ballet/txn/fd_txn.h +++ b/src/ballet/txn/fd_txn.h @@ -107,6 +107,10 @@ transaction, using fd_txn_parse() verification rules. */ #define FD_TXN_MIN_SERIALIZED_SZ (134UL) +/* FD_TXN_MAX_BLOCK_HEIGHT: Max number of accepted blockhashes before a + transaction expires. */ +#define FD_TXN_MAX_BLOCK_HEIGHT 151UL + /* BEGIN Agave limits */ /* "Maximum number of accounts that a transaction may lock. diff --git a/src/disco/bundle/fd_bundle_client.c b/src/disco/bundle/fd_bundle_client.c index aa3219349f6..71f292c66dd 100644 --- a/src/disco/bundle/fd_bundle_client.c +++ b/src/disco/bundle/fd_bundle_client.c @@ -462,12 +462,12 @@ fd_bundle_tile_publish_bundle_txn( fd_txn_m_t * txnm = fd_chunk_to_laddr( ctx->verify_out.mem, ctx->verify_out.chunk ); *txnm = (fd_txn_m_t) { - .reference_slot = 0UL, - .payload_sz = (ushort)txn_sz, - .txn_t_sz = 0U, - .source_ipv4 = source_ipv4, - .source_tpu = FD_TXN_M_TPU_SOURCE_BUNDLE, - .block_engine = { + .reference_block_height = 0UL, + .payload_sz = (ushort)txn_sz, + .txn_t_sz = 0U, + .source_ipv4 = source_ipv4, + .source_tpu = FD_TXN_M_TPU_SOURCE_BUNDLE, + .block_engine = { .bundle_id = ctx->bundle_seq, .bundle_txn_cnt = bundle_txn_cnt, .commission = (uchar)ctx->builder_commission @@ -500,12 +500,12 @@ fd_bundle_tile_publish_txn( ) { fd_txn_m_t * txnm = fd_chunk_to_laddr( ctx->verify_out.mem, ctx->verify_out.chunk ); *txnm = (fd_txn_m_t) { - .reference_slot = 0UL, - .payload_sz = (ushort)txn_sz, - .txn_t_sz = 0U, - .source_ipv4 = source_ipv4, - .source_tpu = FD_TXN_M_TPU_SOURCE_BUNDLE, - .block_engine = { + .reference_block_height = 0UL, + .payload_sz = (ushort)txn_sz, + .txn_t_sz = 0U, + .source_ipv4 = source_ipv4, + .source_tpu = FD_TXN_M_TPU_SOURCE_BUNDLE, + .block_engine = { .bundle_id = 0UL, .bundle_txn_cnt = 1UL, .commission = 0U, diff --git a/src/disco/fd_txn_m.h b/src/disco/fd_txn_m.h index 7bb704d9fd6..1ad1062108f 100644 --- a/src/disco/fd_txn_m.h +++ b/src/disco/fd_txn_m.h @@ -13,10 +13,10 @@ #define FD_TXN_M_TPU_SOURCE_SEND (5UL) struct fd_txn_m { - /* The computed slot that this transaction is referencing, aka. the - slot number of the reference_blockhash. If it could not be - determined, this will be the current slot. */ - ulong reference_slot; + /* The computed block height that this transaction is referencing, aka. the + block height of the slot having the reference_blockhash. If it could not be + determined, this will be block height of the current slot. */ + ulong reference_block_height; ushort payload_sz; diff --git a/src/disco/fd_txn_p.h b/src/disco/fd_txn_p.h index 98654105d3c..cec5e3119ba 100644 --- a/src/disco/fd_txn_p.h +++ b/src/disco/fd_txn_p.h @@ -15,7 +15,7 @@ struct __attribute__((aligned(64))) fd_txn_p { uint rebated_cus; /* requested_exec_plus_acct_data_cus-actual used CUs. Pack reads this for CU rebating. */ uint actual_consumed_cus; /* non_execution_cus+real execution CUs+real account data cus. PoH reads this for block CU counting. */ } bank_cu; /* Populated by bank. */ - ulong blockhash_slot; /* Slot provided by resolv tile when txn arrives at the pack tile. Used when txn is in extra storage in pack. */ + ulong block_height; /* Block height provided by resolv tile when txn arrives at the pack tile. Used when txn is in extra storage in pack. */ }; /* The time that the transaction arrived to the pack tile in ticks. Set by pack and intended to be read from a transaction on a pack->bank link. */ long scheduler_arrival_time_nanos; diff --git a/src/disco/pack/fd_pack.c b/src/disco/pack/fd_pack.c index dbcd81d3091..3db1fa2763a 100644 --- a/src/disco/pack/fd_pack.c +++ b/src/disco/pack/fd_pack.c @@ -1977,7 +1977,7 @@ fd_pack_schedule_impl( fd_pack_t * pack, /* Copied out to 1280 bytes, which copies some other fields we needed to copy anyway. */ FD_STATIC_ASSERT( offsetof(fd_txn_p_t, payload_sz )+sizeof(((fd_txn_p_t*)NULL)->payload_sz )<=1280UL, nt_memcpy ); - FD_STATIC_ASSERT( offsetof(fd_txn_p_t, blockhash_slot )+sizeof(((fd_txn_p_t*)NULL)->blockhash_slot)<=1280UL, nt_memcpy ); + FD_STATIC_ASSERT( offsetof(fd_txn_p_t, block_height )+sizeof(((fd_txn_p_t*)NULL)->block_height )<=1280UL, nt_memcpy ); FD_STATIC_ASSERT( offsetof(fd_txn_p_t, scheduler_arrival_time_nanos )+sizeof(((fd_txn_p_t*)NULL)->scheduler_arrival_time_nanos )<=1280UL, nt_memcpy ); FD_STATIC_ASSERT( offsetof(fd_txn_p_t, source_tpu )+sizeof(((fd_txn_p_t*)NULL)->source_tpu )<=1280UL, nt_memcpy ); FD_STATIC_ASSERT( offsetof(fd_txn_p_t, source_ipv4 )+sizeof(((fd_txn_p_t*)NULL)->source_ipv4 )<=1280UL, nt_memcpy ); diff --git a/src/disco/pack/fd_pack_tile.c b/src/disco/pack/fd_pack_tile.c index 59a64ccaaf5..1094db8ef9f 100644 --- a/src/disco/pack/fd_pack_tile.c +++ b/src/disco/pack/fd_pack_tile.c @@ -34,12 +34,6 @@ transactions/microblock, that's 62k txn/sec/bank. */ #define MICROBLOCK_DURATION_NS (0L) -/* There are 151 accepted blockhashes, but those don't include skips. - This check is neither precise nor accurate, but just good enough. - The bank tile does the final check. We give a little margin for a - few percent skip rate. */ -#define TRANSACTION_LIFETIME_SLOTS 160UL - /* Time is normally a long, but pack expects a ulong. Add -LONG_MIN to the time values so that LONG_MIN maps to 0, LONG_MAX maps to ULONG_MAX, and everything in between maps linearly with a slope of 1. @@ -213,10 +207,9 @@ typedef struct { successful transaction insert. */ long last_successful_insert; - /* highest_observed_slot stores the highest slot number we've seen - from any transaction coming from the resolv tile. When this - increases, we expire old transactions. */ - ulong highest_observed_slot; + /* root_block_height stores the block height of the last rooted slot we've seen + coming from the resolv tile. When this increases, we expire old transactions. */ + ulong root_block_height; /* microblock_duration_ns, and wait_duration respectively scaled to be in ticks instead of nanoseconds */ @@ -276,7 +269,7 @@ typedef struct { ulong id; ulong txn_cnt; ulong txn_received; - ulong min_blockhash_slot; + ulong min_block_height; fd_txn_e_t * _txn[ FD_PACK_MAX_TXN_PER_BUNDLE ]; fd_txn_e_t * const * bundle; /* points to _txn when non-NULL */ } current_bundle[1]; @@ -861,8 +854,8 @@ during_frag( fd_pack_ctx_t * ctx, ulong addr_table_sz = 32UL*txn->addr_table_adtl_cnt; FD_TEST( addr_table_sz<=32UL*FD_TXN_ACCT_ADDR_MAX ); - if( FD_UNLIKELY( (ctx->leader_slot==ULONG_MAX) & (sig>ctx->highest_observed_slot) ) ) { - /* Using the resolv tile's knowledge of the current slot is a bit + if( FD_UNLIKELY( (ctx->leader_slot==ULONG_MAX) & (sig>ctx->root_block_height) ) ) { + /* Using the resolv tile's knowledge of the current last root block height is a bit of a hack, since we don't get any info if there are no transactions and we're not leader. We're actually in exactly the case where that's okay though. The point of calling @@ -870,8 +863,8 @@ during_frag( fd_pack_ctx_t * ctx, drop new but low-fee-paying transactions when pack is clogged with expired but high-fee-paying transactions. That can only happen if we are getting transactions. */ - ctx->highest_observed_slot = sig; - ulong exp_cnt = fd_pack_expire_before( ctx->pack, fd_ulong_max( ctx->highest_observed_slot, TRANSACTION_LIFETIME_SLOTS )-TRANSACTION_LIFETIME_SLOTS ); + ctx->root_block_height = sig; + ulong exp_cnt = fd_pack_expire_before( ctx->pack, fd_ulong_max( ctx->root_block_height, FD_TXN_MAX_BLOCK_HEIGHT )-FD_TXN_MAX_BLOCK_HEIGHT ); FD_MCNT_INC( PACK, TRANSACTION_EXPIRED, exp_cnt ); } @@ -886,7 +879,7 @@ during_frag( fd_pack_ctx_t * ctx, } ctx->current_bundle->id = bundle_id; ctx->current_bundle->txn_cnt = txnm->block_engine.bundle_txn_cnt; - ctx->current_bundle->min_blockhash_slot = ULONG_MAX; + ctx->current_bundle->min_block_height = ULONG_MAX; ctx->current_bundle->txn_received = 0UL; if( FD_UNLIKELY( ctx->current_bundle->txn_cnt==0UL ) ) { @@ -899,8 +892,8 @@ during_frag( fd_pack_ctx_t * ctx, ctx->current_bundle->bundle = fd_pack_insert_bundle_init( ctx->pack, ctx->current_bundle->_txn, ctx->current_bundle->txn_cnt ); } - ctx->cur_spot = ctx->current_bundle->bundle[ ctx->current_bundle->txn_received ]; - ctx->current_bundle->min_blockhash_slot = fd_ulong_min( ctx->current_bundle->min_blockhash_slot, sig ); + ctx->cur_spot = ctx->current_bundle->bundle[ ctx->current_bundle->txn_received ]; + ctx->current_bundle->min_block_height = fd_ulong_min( ctx->current_bundle->min_block_height, txnm->reference_block_height ); } else { ctx->is_bundle = 0; #if FD_PACK_USE_EXTRA_STORAGE @@ -916,7 +909,7 @@ during_frag( fd_pack_ctx_t * ctx, /* We want to store the current time in cur_spot so that we can track its expiration better. We just stash it in the CU fields, since those aren't important right now. */ - ctx->cur_spot->txnp->blockhash_slot = sig; + ctx->cur_spot->txnp->blockhash_block_height = sig; ctx->insert_to_extra = 1; FD_MCNT_INC( PACK, TRANSACTION_INSERTED_TO_EXTRA, 1UL ); } @@ -1006,7 +999,7 @@ after_frag( fd_pack_ctx_t * ctx, } ctx->leader_slot = leader_slot; - ulong exp_cnt = fd_pack_expire_before( ctx->pack, fd_ulong_max( ctx->leader_slot, TRANSACTION_LIFETIME_SLOTS )-TRANSACTION_LIFETIME_SLOTS ); + ulong exp_cnt = fd_pack_expire_before( ctx->pack, fd_ulong_max( ctx->_became_leader->block_height, FD_TXN_MAX_BLOCK_HEIGHT )-FD_TXN_MAX_BLOCK_HEIGHT ); FD_MCNT_INC( PACK, TRANSACTION_EXPIRED, exp_cnt ); ctx->leader_bank = ctx->_became_leader->bank; @@ -1076,7 +1069,7 @@ after_frag( fd_pack_ctx_t * ctx, if( FD_UNLIKELY( ++(ctx->current_bundle->txn_received)==ctx->current_bundle->txn_cnt ) ) { ulong deleted; long insert_duration = -fd_tickcount(); - int result = fd_pack_insert_bundle_fini( ctx->pack, ctx->current_bundle->bundle, ctx->current_bundle->txn_cnt, ctx->current_bundle->min_blockhash_slot, 0, ctx->blk_engine_cfg, &deleted ); + int result = fd_pack_insert_bundle_fini( ctx->pack, ctx->current_bundle->bundle, ctx->current_bundle->txn_cnt, ctx->current_bundle->min_block_height, 0, ctx->blk_engine_cfg, &deleted ); insert_duration += fd_tickcount(); FD_MCNT_INC( PACK, TRANSACTION_DELETED, deleted ); ctx->insert_result[ result + FD_PACK_INSERT_RETVAL_OFF ] += ctx->current_bundle->txn_received; @@ -1084,10 +1077,10 @@ after_frag( fd_pack_ctx_t * ctx, ctx->current_bundle->bundle = NULL; } } else { - ulong blockhash_slot = sig; + ulong block_height = sig; ulong deleted; long insert_duration = -fd_tickcount(); - int result = fd_pack_insert_txn_fini( ctx->pack, ctx->cur_spot, blockhash_slot, &deleted ); + int result = fd_pack_insert_txn_fini( ctx->pack, ctx->cur_spot, block_height, &deleted ); insert_duration += fd_tickcount(); FD_MCNT_INC( PACK, TRANSACTION_DELETED, deleted ); ctx->insert_result[ result + FD_PACK_INSERT_RETVAL_OFF ]++; @@ -1270,7 +1263,7 @@ unprivileged_init( fd_topo_t * topo, ctx->rng = rng; ctx->ticks_per_ns = fd_tempo_tick_per_ns( NULL ); ctx->last_successful_insert = 0L; - ctx->highest_observed_slot = 0UL; + ctx->root_block_height = 0UL; ctx->microblock_duration_ticks = (ulong)(fd_tempo_tick_per_ns( NULL )*(double)MICROBLOCK_DURATION_NS + 0.5); #if FD_PACK_USE_EXTRA_STORAGE ctx->insert_to_extra = 0; diff --git a/src/disco/tiles.h b/src/disco/tiles.h index 832bbde1c0f..e540f4f7be0 100644 --- a/src/disco/tiles.h +++ b/src/disco/tiles.h @@ -36,7 +36,8 @@ struct __attribute__((aligned(FD_CHUNK_ALIGN))) fd_shred34 { typedef struct fd_shred34 fd_shred34_t; struct fd_became_leader { - ulong slot; + ulong slot; + ulong block_height; /* Start and end time of the slot in nanoseconds (from fd_log_wallclock()). */ @@ -105,12 +106,13 @@ typedef struct fd_became_leader fd_became_leader_t; struct fd_rooted_bank { void * bank; ulong slot; + ulong block_height; }; typedef struct fd_rooted_bank fd_rooted_bank_t; struct fd_completed_bank { - ulong slot; + ulong block_height; uchar hash[32]; }; diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c index 8d9ebd819ae..e166f4ebaa1 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -1065,6 +1065,7 @@ publish_root_advanced( fd_replay_tile_t * ctx, fd_replay_root_advanced_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ); msg->bank_idx = bank->idx; + msg->block_height = fd_bank_block_height_get( bank ); fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_ROOT_ADVANCED, ctx->replay_out->chunk, sizeof(fd_replay_root_advanced_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) ); ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_replay_root_advanced_t), ctx->replay_out->chunk0, ctx->replay_out->wmark ); diff --git a/src/discof/replay/fd_replay_tile.h b/src/discof/replay/fd_replay_tile.h index f292681d07b..54989bce268 100644 --- a/src/discof/replay/fd_replay_tile.h +++ b/src/discof/replay/fd_replay_tile.h @@ -43,6 +43,7 @@ typedef struct fd_replay_slot_completed fd_replay_slot_completed_t; struct fd_replay_root_advanced { ulong bank_idx; + ulong block_height; }; typedef struct fd_replay_root_advanced fd_replay_root_advanced_t; diff --git a/src/discof/resolv/fd_resolv_tile.c b/src/discof/resolv/fd_resolv_tile.c index af2c5cd72f0..beca0bf6a0d 100644 --- a/src/discof/resolv/fd_resolv_tile.c +++ b/src/discof/resolv/fd_resolv_tile.c @@ -25,7 +25,7 @@ typedef struct blockhash blockhash_t; struct blockhash_map { blockhash_t key; - ulong slot; + ulong block_height; }; typedef struct blockhash_map blockhash_map_t; @@ -133,7 +133,7 @@ typedef struct { blockhash_map_t * blockhash_map; - ulong flushing_slot; + ulong flushing_block_height; ulong flush_pool_idx; /* In the full client, the resolv tile is passed only a rooted bank @@ -154,7 +154,8 @@ typedef struct { map_chain_t * map_chain; lru_list_t lru_list[1]; - ulong completed_slot; + ulong completed_block_height; + ulong root_block_height; ulong blockhash_ring_idx; blockhash_t blockhash_ring[ BLOCKHASH_RING_LEN ]; @@ -345,7 +346,7 @@ publish_txn( fd_resolv_ctx_t * ctx, fd_txn_t const * txnt = fd_txn_m_txn_t( txnm ); - txnm->reference_slot = ctx->flushing_slot; + txnm->reference_block_height = ctx->flushing_block_height; if( FD_UNLIKELY( txnt->addr_table_adtl_cnt ) ) { if( FD_UNLIKELY( !ctx->bank ) ) { @@ -358,7 +359,7 @@ publish_txn( fd_resolv_ctx_t * ctx, ulong realized_sz = fd_txn_m_realized_footprint( txnm, 1, 1 ); ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() ); - fd_stem_publish( stem, 0UL, txnm->reference_slot, ctx->out_pack->chunk, realized_sz, 0UL, 0UL, tspub ); + fd_stem_publish( stem, 0UL, ctx->root_block_height, ctx->out_pack->chunk, realized_sz, 0UL, 0UL, tspub ); ctx->out_pack->chunk = fd_dcache_compact_next( ctx->out_pack->chunk, realized_sz, ctx->out_pack->chunk0, ctx->out_pack->wmark ); return 1; @@ -435,13 +436,13 @@ after_frag( fd_resolv_ctx_t * ctx, ctx->blockhash_ring_idx++; blockhash_map_t * blockhash = map_insert( ctx->blockhash_map, *(blockhash_t *)msg->block_hash.uc ); - blockhash->slot = msg->slot; + blockhash->block_height = msg->block_height; blockhash_t * hash = (blockhash_t *)msg->block_hash.uc; ctx->flush_pool_idx = map_chain_idx_query_const( ctx->map_chain, &hash, ULONG_MAX, ctx->pool ); - ctx->flushing_slot = msg->slot; + ctx->flushing_block_height = msg->block_height; - ctx->completed_slot = msg->slot; + ctx->completed_block_height = msg->block_height; break; } case REPLAY_SIG_ROOT_ADVANCED: { @@ -509,11 +510,11 @@ after_frag( fd_resolv_ctx_t * ctx, return; } - txnm->reference_slot = ctx->completed_slot; + txnm->reference_block_height = ctx->completed_block_height; blockhash_map_t const * blockhash = map_query_const( ctx->blockhash_map, *(blockhash_t*)( fd_txn_m_payload( txnm )+txnt->recent_blockhash_off ), NULL ); if( FD_LIKELY( blockhash ) ) { - txnm->reference_slot = blockhash->slot; - if( FD_UNLIKELY( txnm->reference_slot+151ULcompleted_slot ) ) { + txnm->reference_block_height = blockhash->block_height; + if( FD_UNLIKELY( txnm->reference_block_height+FD_TXN_MAX_BLOCK_HEIGHTroot_block_height ) ) { if( FD_UNLIKELY( txnm->block_engine.bundle_id ) ) ctx->bundle_failed = 1; ctx->metrics.blockhash_expired++; return; @@ -569,7 +570,7 @@ after_frag( fd_resolv_ctx_t * ctx, ulong realized_sz = fd_txn_m_realized_footprint( txnm, 1, 1 ); ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() ); - fd_stem_publish( stem, 0UL, txnm->reference_slot, ctx->out_pack->chunk, realized_sz, 0UL, tsorig, tspub ); + fd_stem_publish( stem, 0UL, ctx->root_block_height, ctx->out_pack->chunk, realized_sz, 0UL, tsorig, tspub ); ctx->out_pack->chunk = fd_dcache_compact_next( ctx->out_pack->chunk, realized_sz, ctx->out_pack->chunk0, ctx->out_pack->wmark ); } @@ -587,7 +588,8 @@ unprivileged_init( fd_topo_t * topo, ctx->bundle_failed = 0; ctx->bundle_id = 0UL; - ctx->completed_slot = 0UL; + ctx->completed_block_height = 0UL; + ctx->root_block_height = 0UL; ctx->blockhash_ring_idx = 0UL; ctx->flush_pool_idx = ULONG_MAX; diff --git a/src/discoh/poh/fd_poh_tile.c b/src/discoh/poh/fd_poh_tile.c index c598381a2a6..c3753e5da68 100644 --- a/src/discoh/poh/fd_poh_tile.c +++ b/src/discoh/poh/fd_poh_tile.c @@ -978,6 +978,7 @@ fd_ext_bank_load_account( void const * bank, CALLED_FROM_RUST static void publish_became_leader( fd_poh_ctx_t * ctx, ulong slot, + ulong block_height, ulong epoch ) { double tick_per_ns = fd_tempo_tick_per_ns( NULL ); fd_histf_sample( ctx->begin_leader_delay, (ulong)((double)(fd_log_wallclock()-ctx->reset_slot_start_ns)/tick_per_ns) ); @@ -1035,6 +1036,7 @@ publish_became_leader( fd_poh_ctx_t * ctx, leader->max_microblocks_in_slot = ctx->max_microblocks_per_slot; leader->ticks_per_slot = ctx->ticks_per_slot; leader->total_skipped_ticks = ctx->ticks_per_slot*(slot-ctx->reset_slot); + leader->block_height = block_height; leader->epoch = epoch; leader->bundle->config[0] = config[0]; @@ -1068,6 +1070,7 @@ publish_became_leader( fd_poh_ctx_t * ctx, CALLED_FROM_RUST void fd_ext_poh_begin_leader( void const * bank, ulong slot, + ulong block_height, ulong epoch, ulong hashcnt_per_tick, ulong cus_block_limit, @@ -1150,7 +1153,7 @@ fd_ext_poh_begin_leader( void const * bank, FD_TEST( ctx->highwater_leader_slot==ULONG_MAX || slot>=ctx->highwater_leader_slot ); ctx->highwater_leader_slot = fd_ulong_max( fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot ), slot ); - publish_became_leader( ctx, slot, epoch ); + publish_became_leader( ctx, slot, block_height, epoch ); FD_LOG_INFO(( "fd_ext_poh_begin_leader(slot=%lu, highwater_leader_slot=%lu, last_slot=%lu, last_hashcnt=%lu)", slot, ctx->highwater_leader_slot, ctx->last_slot, ctx->last_hashcnt )); fd_ext_poh_write_unlock(); diff --git a/src/discoh/resolv/fd_resolv_tile.c b/src/discoh/resolv/fd_resolv_tile.c index af43ff4898e..eca056181a2 100644 --- a/src/discoh/resolv/fd_resolv_tile.c +++ b/src/discoh/resolv/fd_resolv_tile.c @@ -21,7 +21,7 @@ typedef struct blockhash blockhash_t; struct blockhash_map { blockhash_t key; - ulong slot; + ulong block_height; }; typedef struct blockhash_map blockhash_map_t; @@ -122,17 +122,18 @@ typedef struct { void * root_bank; ulong root_slot; + ulong root_block_height; blockhash_map_t * blockhash_map; - ulong flushing_slot; + ulong flushing_block_height; ulong flush_pool_idx; fd_stashed_txn_m_t * pool; map_chain_t * map_chain; lru_list_t lru_list[1]; - ulong completed_slot; + ulong completed_block_height; ulong blockhash_ring_idx; blockhash_t blockhash_ring[ BLOCKHASH_RING_LEN ]; @@ -236,7 +237,7 @@ publish_txn( fd_resolv_ctx_t * ctx, fd_txn_t const * txnt = fd_txn_m_txn_t( txnm ); - txnm->reference_slot = ctx->flushing_slot; + txnm->reference_block_height = ctx->flushing_block_height; if( FD_UNLIKELY( txnt->addr_table_adtl_cnt ) ) { if( FD_UNLIKELY( !ctx->root_bank ) ) { @@ -253,7 +254,7 @@ publish_txn( fd_resolv_ctx_t * ctx, ulong realized_sz = fd_txn_m_realized_footprint( txnm, 1, 1 ); ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() ); - fd_stem_publish( stem, 0UL, txnm->reference_slot, ctx->out_chunk, realized_sz, 0UL, 0UL, tspub ); + fd_stem_publish( stem, 0UL, ctx->root_block_height, ctx->out_chunk, realized_sz, 0UL, 0UL, tspub ); ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, realized_sz, ctx->out_chunk0, ctx->out_wmark ); return 1; @@ -323,12 +324,13 @@ after_frag( fd_resolv_ctx_t * ctx, ctx->root_bank = frag->bank; ctx->root_slot = frag->slot; + ctx->root_block_height = frag->block_height; break; } case 1: { fd_completed_bank_t * frag = (fd_completed_bank_t *)ctx->_bank_msg; - /* blockhash_ring is initalized to all zeros. blockhash=0 is an illegal map query */ + /* blockhash_ring is initialized to all zeros. blockhash=0 is an illegal map query */ if( FD_UNLIKELY( memcmp( &ctx->blockhash_ring[ ctx->blockhash_ring_idx%BLOCKHASH_RING_LEN ], (uchar[ 32UL ]){ 0UL }, sizeof(blockhash_t) ) ) ) { blockhash_map_t * entry = map_query( ctx->blockhash_map, ctx->blockhash_ring[ ctx->blockhash_ring_idx%BLOCKHASH_RING_LEN ], NULL ); if( FD_LIKELY( entry ) ) map_remove( ctx->blockhash_map, entry ); @@ -338,13 +340,13 @@ after_frag( fd_resolv_ctx_t * ctx, ctx->blockhash_ring_idx++; blockhash_map_t * blockhash = map_insert( ctx->blockhash_map, *(blockhash_t *)frag->hash ); - blockhash->slot = frag->slot; + blockhash->block_height = frag->block_height; blockhash_t * hash = (blockhash_t *)frag->hash; ctx->flush_pool_idx = map_chain_idx_query_const( ctx->map_chain, &hash, ULONG_MAX, ctx->pool ); - ctx->flushing_slot = frag->slot; + ctx->flushing_block_height = frag->block_height; - ctx->completed_slot = frag->slot; + ctx->completed_block_height = frag->block_height; break; } default: @@ -391,11 +393,11 @@ after_frag( fd_resolv_ctx_t * ctx, return; } - txnm->reference_slot = ctx->completed_slot; + txnm->reference_block_height = ctx->completed_block_height; blockhash_map_t const * blockhash = map_query_const( ctx->blockhash_map, *(blockhash_t*)( fd_txn_m_payload( txnm )+txnt->recent_blockhash_off ), NULL ); if( FD_LIKELY( blockhash ) ) { - txnm->reference_slot = blockhash->slot; - if( FD_UNLIKELY( txnm->reference_slot+151ULcompleted_slot ) ) { + txnm->reference_block_height = blockhash->block_height; + if( FD_UNLIKELY( txnm->reference_block_height+FD_TXN_MAX_BLOCK_HEIGHTroot_block_height ) ) { if( FD_UNLIKELY( txnm->block_engine.bundle_id ) ) ctx->bundle_failed = 1; ctx->metrics.blockhash_expired++; return; @@ -454,7 +456,7 @@ after_frag( fd_resolv_ctx_t * ctx, ulong realized_sz = fd_txn_m_realized_footprint( txnm, 1, 1 ); ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() ); - fd_stem_publish( stem, 0UL, txnm->reference_slot, ctx->out_chunk, realized_sz, 0UL, tsorig, tspub ); + fd_stem_publish( stem, 0UL, ctx->root_block_height, ctx->out_chunk, realized_sz, 0UL, tsorig, tspub ); ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, realized_sz, ctx->out_chunk0, ctx->out_wmark ); } @@ -472,7 +474,7 @@ unprivileged_init( fd_topo_t * topo, ctx->bundle_failed = 0; ctx->bundle_id = 0UL; - ctx->completed_slot = 0UL; + ctx->completed_block_height = 0UL; ctx->blockhash_ring_idx = 0UL; ctx->flush_pool_idx = ULONG_MAX; @@ -487,7 +489,9 @@ unprivileged_init( fd_topo_t * topo, if( FD_LIKELY( !tile->kind_id ) ) _fd_ext_resolv_tile_cnt = ctx->round_robin_cnt; - ctx->root_bank = NULL; + ctx->root_bank = NULL; + ctx->root_slot = 0UL; + ctx->root_block_height = 0UL; memset( ctx->blockhash_ring, 0, sizeof( ctx->blockhash_ring ) ); memset( &ctx->metrics, 0, sizeof( ctx->metrics ) ); From 5d9e3c04b750658ac6220003a220d540099a816e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Guillemet?= Date: Fri, 7 Nov 2025 21:22:27 +0100 Subject: [PATCH 2/3] Wrap comment --- src/disco/pack/fd_pack_tile.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/disco/pack/fd_pack_tile.c b/src/disco/pack/fd_pack_tile.c index 1094db8ef9f..6bbb27329f0 100644 --- a/src/disco/pack/fd_pack_tile.c +++ b/src/disco/pack/fd_pack_tile.c @@ -855,14 +855,14 @@ during_frag( fd_pack_ctx_t * ctx, FD_TEST( addr_table_sz<=32UL*FD_TXN_ACCT_ADDR_MAX ); if( FD_UNLIKELY( (ctx->leader_slot==ULONG_MAX) & (sig>ctx->root_block_height) ) ) { - /* Using the resolv tile's knowledge of the current last root block height is a bit - of a hack, since we don't get any info if there are no - transactions and we're not leader. We're actually in exactly - the case where that's okay though. The point of calling - expire_before long before we become leader is so that we don't - drop new but low-fee-paying transactions when pack is clogged - with expired but high-fee-paying transactions. That can only - happen if we are getting transactions. */ + /* Using the resolv tile's knowledge of the current last root + block height is a bit of a hack, since we don't get any info if + there are no transactions and we're not leader. We're actually + in exactly the case where that's okay though. The point of + calling expire_before long before we become leader is so that + we don't drop new but low-fee-paying transactions when pack is + clogged with expired but high-fee-paying transactions. + That can only happen if we are getting transactions. */ ctx->root_block_height = sig; ulong exp_cnt = fd_pack_expire_before( ctx->pack, fd_ulong_max( ctx->root_block_height, FD_TXN_MAX_BLOCK_HEIGHT )-FD_TXN_MAX_BLOCK_HEIGHT ); FD_MCNT_INC( PACK, TRANSACTION_EXPIRED, exp_cnt ); From 9d5e913038e0c6cfb1b9e09518a61d11d51afbd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Guillemet?= Date: Fri, 7 Nov 2025 21:52:32 +0100 Subject: [PATCH 3/3] Fix blockhash when extra storage is on. Use `fd_txn_p.reference_block_height` to pass expiration to `fd_pack_txn_fini` in all cases. --- src/disco/fd_txn_p.h | 2 +- src/disco/pack/fd_pack.c | 9 ++++----- src/disco/pack/fd_pack.h | 6 +++--- src/disco/pack/fd_pack_tile.c | 30 ++++++++++++++---------------- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/disco/fd_txn_p.h b/src/disco/fd_txn_p.h index cec5e3119ba..765f49ea1c6 100644 --- a/src/disco/fd_txn_p.h +++ b/src/disco/fd_txn_p.h @@ -15,7 +15,7 @@ struct __attribute__((aligned(64))) fd_txn_p { uint rebated_cus; /* requested_exec_plus_acct_data_cus-actual used CUs. Pack reads this for CU rebating. */ uint actual_consumed_cus; /* non_execution_cus+real execution CUs+real account data cus. PoH reads this for block CU counting. */ } bank_cu; /* Populated by bank. */ - ulong block_height; /* Block height provided by resolv tile when txn arrives at the pack tile. Used when txn is in extra storage in pack. */ + ulong reference_block_height; /* Block height provided by resolv tile when txn arrives at the pack tile. */ }; /* The time that the transaction arrived to the pack tile in ticks. Set by pack and intended to be read from a transaction on a pack->bank link. */ long scheduler_arrival_time_nanos; diff --git a/src/disco/pack/fd_pack.c b/src/disco/pack/fd_pack.c index 3db1fa2763a..087abaa5872 100644 --- a/src/disco/pack/fd_pack.c +++ b/src/disco/pack/fd_pack.c @@ -1284,7 +1284,6 @@ populate_bitsets( fd_pack_t * pack, int fd_pack_insert_txn_fini( fd_pack_t * pack, fd_txn_e_t * txne, - ulong expires_at, ulong * delete_cnt ) { *delete_cnt = 0UL; @@ -1299,7 +1298,7 @@ fd_pack_insert_txn_fini( fd_pack_t * pack, accessed with adj_lut[n]. */ fd_acct_addr_t const * alt_adj = ord->txn_e->alt_accts - fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_IMM ); - ord->expires_at = expires_at; + ord->expires_at = txne->txnp->reference_block_height; int est_result = fd_pack_estimate_rewards_and_compute( txne, ord ); if( FD_UNLIKELY( !est_result ) ) REJECT( ESTIMATION_FAIL ); @@ -1318,7 +1317,7 @@ fd_pack_insert_txn_fini( fd_pack_t * pack, } /* Reject any transactions that have already expired */ - if( FD_UNLIKELY( expires_atexpire_before ) ) REJECT( EXPIRED ); + if( FD_UNLIKELY( ord->expires_atexpire_before ) ) REJECT( EXPIRED ); int replaces = 0; /* If it's a durable nonce and we already have one, delete one or the @@ -1388,7 +1387,7 @@ fd_pack_insert_txn_fini( fd_pack_t * pack, if( FD_UNLIKELY( is_durable_nonce ) ) noncemap_ele_insert( pack->noncemap, ord, pack->pool ); - fd_pack_expq_t temp[ 1 ] = {{ .expires_at = expires_at, .txn = ord }}; + fd_pack_expq_t temp[ 1 ] = {{ .expires_at = ord->expires_at, .txn = ord }}; expq_insert( pack->expiration_q, temp ); if( FD_LIKELY( is_vote ) ) insert_into = pack->pending_votes; @@ -1977,7 +1976,7 @@ fd_pack_schedule_impl( fd_pack_t * pack, /* Copied out to 1280 bytes, which copies some other fields we needed to copy anyway. */ FD_STATIC_ASSERT( offsetof(fd_txn_p_t, payload_sz )+sizeof(((fd_txn_p_t*)NULL)->payload_sz )<=1280UL, nt_memcpy ); - FD_STATIC_ASSERT( offsetof(fd_txn_p_t, block_height )+sizeof(((fd_txn_p_t*)NULL)->block_height )<=1280UL, nt_memcpy ); + FD_STATIC_ASSERT( offsetof(fd_txn_p_t, reference_block_height )+sizeof(((fd_txn_p_t*)NULL)->reference_block_height )<=1280UL, nt_memcpy ); FD_STATIC_ASSERT( offsetof(fd_txn_p_t, scheduler_arrival_time_nanos )+sizeof(((fd_txn_p_t*)NULL)->scheduler_arrival_time_nanos )<=1280UL, nt_memcpy ); FD_STATIC_ASSERT( offsetof(fd_txn_p_t, source_tpu )+sizeof(((fd_txn_p_t*)NULL)->source_tpu )<=1280UL, nt_memcpy ); FD_STATIC_ASSERT( offsetof(fd_txn_p_t, source_ipv4 )+sizeof(((fd_txn_p_t*)NULL)->source_ipv4 )<=1280UL, nt_memcpy ); diff --git a/src/disco/pack/fd_pack.h b/src/disco/pack/fd_pack.h index ad4f003b92f..63e7b8aaf3f 100644 --- a/src/disco/pack/fd_pack.h +++ b/src/disco/pack/fd_pack.h @@ -333,9 +333,9 @@ FD_STATIC_ASSERT( FD_PACK_INSERT_ACCEPT_NONCE_NONVOTE_REPLACEtxnp->scheduler_arrival_time_nanos = insert->txnp->scheduler_arrival_time_nanos; extra_txn_deq_remove_head( ctx->extra_txn_deq ); - ulong blockhash_slot = insert->txnp->blockhash_slot; - ulong deleted; long insert_duration = -fd_tickcount(); - int result = fd_pack_insert_txn_fini( ctx->pack, spot, blockhash_slot, &deleted ); + int result = fd_pack_insert_txn_fini( ctx->pack, spot, &deleted ); insert_duration += fd_tickcount(); FD_MCNT_INC( PACK, TRANSACTION_DELETED, deleted ); @@ -842,11 +840,12 @@ during_frag( fd_pack_ctx_t * ctx, if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>FD_TPU_RESOLVED_MTU ) ) FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark )); - fd_txn_m_t * txnm = (fd_txn_m_t *)dcache_entry; - ulong payload_sz = txnm->payload_sz; - ulong txn_t_sz = txnm->txn_t_sz; - uint source_ipv4 = txnm->source_ipv4; - uchar source_tpu = txnm->source_tpu; + fd_txn_m_t * txnm = (fd_txn_m_t *)dcache_entry; + ulong payload_sz = txnm->payload_sz; + ulong txn_t_sz = txnm->txn_t_sz; + uint source_ipv4 = txnm->source_ipv4; + uchar source_tpu = txnm->source_tpu; + ulong reference_block_height = txnm->reference_block_height; FD_TEST( payload_sz<=FD_TPU_MTU ); FD_TEST( txn_t_sz <=FD_TXN_MAX_SZ ); fd_txn_t * txn = fd_txn_m_txn_t( txnm ); @@ -893,7 +892,7 @@ during_frag( fd_pack_ctx_t * ctx, ctx->current_bundle->bundle = fd_pack_insert_bundle_init( ctx->pack, ctx->current_bundle->_txn, ctx->current_bundle->txn_cnt ); } ctx->cur_spot = ctx->current_bundle->bundle[ ctx->current_bundle->txn_received ]; - ctx->current_bundle->min_block_height = fd_ulong_min( ctx->current_bundle->min_block_height, txnm->reference_block_height ); + ctx->current_bundle->min_block_height = fd_ulong_min( ctx->current_bundle->min_block_height, reference_block_height ); } else { ctx->is_bundle = 0; #if FD_PACK_USE_EXTRA_STORAGE @@ -906,11 +905,7 @@ during_frag( fd_pack_ctx_t * ctx, FD_MCNT_INC( PACK, TRANSACTION_DROPPED_FROM_EXTRA, 1UL ); } ctx->cur_spot = extra_txn_deq_peek_tail( extra_txn_deq_insert_tail( ctx->extra_txn_deq ) ); - /* We want to store the current time in cur_spot so that we can - track its expiration better. We just stash it in the CU - fields, since those aren't important right now. */ - ctx->cur_spot->txnp->blockhash_block_height = sig; - ctx->insert_to_extra = 1; + ctx->insert_to_extra = 1; FD_MCNT_INC( PACK, TRANSACTION_INSERTED_TO_EXTRA, 1UL ); } #else @@ -929,6 +924,10 @@ during_frag( fd_pack_ctx_t * ctx, ctx->cur_spot->txnp->payload_sz = payload_sz; ctx->cur_spot->txnp->source_ipv4 = source_ipv4; ctx->cur_spot->txnp->source_tpu = source_tpu; + /* We just stash the reference block height in the CU + fields, since those aren't important until + fd_pack_insert_txn_fini is called. */ + ctx->cur_spot->txnp->reference_block_height = reference_block_height; break; } @@ -1077,10 +1076,9 @@ after_frag( fd_pack_ctx_t * ctx, ctx->current_bundle->bundle = NULL; } } else { - ulong block_height = sig; ulong deleted; long insert_duration = -fd_tickcount(); - int result = fd_pack_insert_txn_fini( ctx->pack, ctx->cur_spot, block_height, &deleted ); + int result = fd_pack_insert_txn_fini( ctx->pack, ctx->cur_spot, &deleted ); insert_duration += fd_tickcount(); FD_MCNT_INC( PACK, TRANSACTION_DELETED, deleted ); ctx->insert_result[ result + FD_PACK_INSERT_RETVAL_OFF ]++;