From 5c0932c93619d6d31194106e82288371d22f75ce Mon Sep 17 00:00:00 2001 From: Charles Li Date: Tue, 7 Jan 2025 21:07:48 +0000 Subject: [PATCH] refactor(tower): rework tower in-mem repr and cleanup API --- src/app/fdctl/run/tiles/fd_replay.c | 67 ++++---- src/choreo/tower/fd_tower.c | 253 +++++++++++++--------------- src/choreo/tower/fd_tower.h | 127 ++++++-------- src/choreo/tower/test_tower.c | 25 +-- src/disco/restart/fd_restart.c | 13 +- src/disco/restart/fd_restart.h | 1 + 6 files changed, 224 insertions(+), 262 deletions(-) diff --git a/src/app/fdctl/run/tiles/fd_replay.c b/src/app/fdctl/run/tiles/fd_replay.c index 90bba273f7..fc38c445f9 100644 --- a/src/app/fdctl/run/tiles/fd_replay.c +++ b/src/app/fdctl/run/tiles/fd_replay.c @@ -250,7 +250,23 @@ struct fd_replay_tile_ctx { ulong bank_cnt; fd_replay_out_ctx_t bank_out[ FD_PACK_MAX_BANK_TILES ]; - ulong * wmk; /* publish watermark */ + ulong root; /* the root slot is the most recent slot to have reached + max lockout in the tower */ + + ulong * wmk; /* publish watermark. The watermark is defined as the + minimum of the tower root (root above) and blockstore + smr (blockstore->smr). The watermark is used to + publish our fork-aware structures eg. blockstore, + forks, ghost. In general, publishing has the effect of + pruning minority forks in those structures, + indicating that is ok to release the memory being + occupied by said forks. + + The reason it has to be the minimum of the two, is the + tower root can lag the SMR and vice versa, but both + the fork-aware structures need to maintain information + through both of those slots. */ + ulong * poh; /* proof-of-history slot */ uint poh_init_done; int snapshot_init_done; @@ -448,7 +464,7 @@ during_frag( fd_replay_tile_ctx_t * ctx, Microblock as a list of fd_txn_p_t (sz * sizeof(fd_txn_p_t)) */ ctx->curr_slot = fd_disco_replay_sig_slot( sig ); - if( FD_UNLIKELY( ctx->curr_slot < ctx->tower->root ) ) { + if( FD_UNLIKELY( ctx->curr_slot < fd_fseq_query( ctx->wmk ) ) ) { FD_LOG_WARNING(( "store sent slot %lu before our root.", ctx->curr_slot )); } ctx->flags = fd_disco_replay_sig_flags( sig ); @@ -474,8 +490,8 @@ during_frag( fd_replay_tile_ctx_t * ctx, Microblock bank trailer */ ctx->curr_slot = fd_disco_poh_sig_slot( sig ); - if( FD_UNLIKELY( ctx->curr_slot < ctx->tower->root ) ) { - FD_LOG_WARNING(( "pack sent slot %lu before our root %lu.", ctx->curr_slot, ctx->tower->root )); + if( FD_UNLIKELY( ctx->curr_slot < fd_fseq_query( ctx->wmk ) ) ) { + FD_LOG_WARNING(( "pack sent slot %lu before our watermark %lu.", ctx->curr_slot, fd_fseq_query( ctx->wmk ) )); } if( fd_disco_poh_sig_pkt_type( sig )==POH_PKT_TYPE_MICROBLOCK ) { ulong bank_idx = fd_disco_poh_sig_bank_tile( sig ); @@ -930,7 +946,7 @@ static void send_tower_sync( fd_replay_tile_ctx_t * ctx ) { if( FD_UNLIKELY( !ctx->vote ) ) return; FD_LOG_NOTICE( ( "sending tower sync" ) ); - ulong vote_slot = fd_tower_votes_peek_tail_const( ctx->tower->votes )->slot; + ulong vote_slot = fd_tower_votes_peek_tail_const( ctx->tower )->slot; fd_blockstore_start_read( ctx->blockstore ); fd_hash_t const * vote_bank_hash = fd_blockstore_bank_hash_query( ctx->blockstore, vote_slot ); fd_hash_t const * vote_block_hash = fd_blockstore_block_hash_query( ctx->blockstore, vote_slot ); @@ -953,7 +969,7 @@ send_tower_sync( fd_replay_tile_ctx_t * ctx ) { FD_SCRATCH_SCOPE_BEGIN { fd_txn_p_t * txn = (fd_txn_p_t *)fd_chunk_to_laddr( ctx->sender_out_mem, ctx->sender_out_chunk ); - fd_tower_to_vote_txn( ctx->tower, vote_bank_hash, vote_block_hash, ctx->validator_identity, ctx->vote_authority, ctx->vote_acc, txn ); + fd_tower_to_vote_txn( ctx->tower, ctx->root, vote_bank_hash, vote_block_hash, ctx->validator_identity, ctx->vote_authority, ctx->vote_acc, txn ); } FD_SCRATCH_SCOPE_END; /* TODO: Can use a smaller size, adjusted for payload length */ @@ -974,7 +990,7 @@ send_tower_sync( fd_replay_tile_ctx_t * ctx ) { ctx->sender_out_wmark ); /* Dump the latest sent tower into the tower checkpoint file */ - if( FD_LIKELY( ctx->tower_checkpt_fileno > 0 ) ) fd_restart_tower_checkpt( vote_bank_hash, ctx->tower, ctx->tower_checkpt_fileno ); + if( FD_LIKELY( ctx->tower_checkpt_fileno > 0 ) ) fd_restart_tower_checkpt( vote_bank_hash, ctx->tower, ctx->root, ctx->tower_checkpt_fileno ); } static fd_fork_t * @@ -1148,13 +1164,13 @@ after_frag( fd_replay_tile_ctx_t * ctx, ulong parent_slot = ctx->parent_slot; ulong flags = ctx->flags; ulong bank_idx = ctx->bank_idx; - if( FD_UNLIKELY( curr_slot < ctx->tower->root ) ) { - FD_LOG_WARNING(( "ignoring replay of slot %lu (parent: %lu). earlier than our root %lu.", curr_slot, parent_slot, ctx->tower->root )); + if( FD_UNLIKELY( curr_slot < fd_fseq_query( ctx->wmk ) ) ) { + FD_LOG_WARNING(( "ignoring replay of slot %lu (parent: %lu). earlier than our watermark %lu.", curr_slot, parent_slot, fd_fseq_query( ctx->wmk ) )); return; } - if( FD_UNLIKELY( parent_slot < ctx->tower->root ) ) { - FD_LOG_WARNING(( "ignoring replay of slot %lu (parent: %lu). parent slot is earlier than our root %lu.", curr_slot, parent_slot, ctx->tower->root )); + if( FD_UNLIKELY( parent_slot < fd_fseq_query( ctx->wmk ) ) ) { + FD_LOG_WARNING(( "ignoring replay of slot %lu (parent: %lu). parent slot is earlier than our watermark %lu.", curr_slot, parent_slot, fd_fseq_query( ctx->wmk ) ) ); return; } @@ -1377,7 +1393,7 @@ after_frag( fd_replay_tile_ctx_t * ctx, fd_forks_print( ctx->forks ); fd_ghost_print( ctx->ghost, ctx->epoch, fd_ghost_root( ctx-> ghost ) ); - fd_tower_print( ctx->tower ); + fd_tower_print( ctx->tower, ctx->root ); ulong vote_slot = fd_tower_vote_slot( ctx->tower, ctx->epoch, ctx->funk, child->slot_ctx.funk_txn, ctx->ghost ); @@ -1409,27 +1425,11 @@ after_frag( fd_replay_tile_ctx_t * ctx, /* Vote locally */ - fd_tower_vote( ctx->tower, vote_slot ); + ulong root = fd_tower_vote( ctx->tower, vote_slot ); - /* Check if we've reached max lockout. */ + /* Update to a new root, if there is one. */ - if( FD_UNLIKELY( fd_tower_is_max_lockout( ctx->tower ) ) ) { - - /* Publish tower and get the new root. */ - - ulong root = fd_tower_publish( ctx->tower ); - FD_LOG_NOTICE(( "new tower root: %lu", root )); - - /* Note that our local tower root is not used to publish our - fork-aware structures eg. blockstore, forks, ghost. - - Instead the SMR is used. The main reason to avoid using - tower root is while starting up, the tower will be loaded - from the vote account state (the "cluster tower") which - might have an earlier root slot than the snapshot slot. - The other structures are initialized to the snapshot - slot. */ - } + if ( FD_LIKELY ( root != FD_SLOT_NULL ) ) ctx->root = root; /* optimize for full tower (replay is keeping up) */ } /* Send our updated tower to the cluster. */ @@ -1685,7 +1685,8 @@ init_after_snapshot( fd_replay_tile_ctx_t * ctx ) { fd_memcpy( key.c, ctx->vote_acc, sizeof(fd_pubkey_t) ); key.c[FD_FUNK_REC_KEY_FOOTPRINT - 1] = FD_FUNK_KEY_TYPE_ACC; fd_tower_from_vote_acc( ctx->tower, ctx->funk, snapshot_fork->slot_ctx.funk_txn, &key ); - fd_tower_print( ctx->tower ); + FD_LOG_NOTICE(( "vote account: %s", FD_BASE58_ENC_32_ALLOCA( key.c ) )); + fd_tower_print( ctx->tower, ctx->root ); fd_bank_hash_cmp_t * bank_hash_cmp = ctx->epoch_ctx->bank_hash_cmp; bank_hash_cmp->total_stake = ctx->epoch->total_stake; @@ -1874,7 +1875,7 @@ during_housekeeping( void * _ctx ) { root and blockstore smr. */ fd_blockstore_start_read( ctx->blockstore ); - ulong wmk = fd_ulong_min( ctx->tower->root , ctx->blockstore->smr ); + ulong wmk = fd_ulong_min( ctx->root, ctx->blockstore->smr ); fd_blockstore_end_read( ctx->blockstore ); if ( FD_LIKELY( wmk <= fd_fseq_query( ctx->wmk ) ) ) return; diff --git a/src/choreo/tower/fd_tower.c b/src/choreo/tower/fd_tower.c index 3d27608c28..af0a6a4839 100644 --- a/src/choreo/tower/fd_tower.c +++ b/src/choreo/tower/fd_tower.c @@ -1,11 +1,10 @@ #include "fd_tower.h" - -#define THRESHOLD_DEPTH ( 8 ) -#define THRESHOLD_PCT ( 2.0 / 3.0 ) -#define SHALLOW_THRESHOLD_DEPTH ( 4 ) -#define SHALLOW_THRESHOLD_PCT ( 0.38 ) -#define SWITCH_PCT ( 0.38 ) +#define THRESHOLD_DEPTH (8) +#define THRESHOLD_PCT (2.0 / 3.0) +#define SHALLOW_THRESHOLD_DEPTH (4) +#define SHALLOW_THRESHOLD_PCT (0.38) +#define SWITCH_PCT (0.38) void * fd_tower_new( void * shmem ) { @@ -19,22 +18,7 @@ fd_tower_new( void * shmem ) { return NULL; } - ulong footprint = fd_tower_footprint(); - if( FD_UNLIKELY( !footprint ) ) { - FD_LOG_WARNING(( "bad mem" )); - return NULL; - } - - fd_memset( shmem, 0, footprint ); - ulong laddr = (ulong)shmem; - fd_tower_t * tower = (void *)laddr; - laddr += sizeof( fd_tower_t ); - - laddr = fd_ulong_align_up( laddr, fd_tower_votes_align() ); - tower->votes = fd_tower_votes_new( (void *)laddr ); - laddr += fd_tower_votes_footprint(); - - return shmem; + return fd_tower_votes_new( shmem ); } fd_tower_t * @@ -50,26 +34,18 @@ fd_tower_join( void * shtower ) { return NULL; } - ulong laddr = (ulong)shtower; /* offset from a memory region */ - fd_tower_t * tower = (void *)shtower; - laddr += sizeof(fd_tower_t); - - laddr = fd_ulong_align_up( laddr, fd_tower_votes_align() ); - tower->votes = fd_tower_votes_join( (void *)laddr ); - laddr += fd_tower_votes_footprint(); - - return (fd_tower_t *)shtower; + return fd_tower_votes_join( shtower ); } void * -fd_tower_leave( fd_tower_t const * tower ) { +fd_tower_leave( fd_tower_t * tower ) { if( FD_UNLIKELY( !tower ) ) { FD_LOG_WARNING(( "NULL tower" )); return NULL; } - return (void *)tower; + return fd_tower_votes_leave( tower ); } void * @@ -85,24 +61,24 @@ fd_tower_delete( void * tower ) { return NULL; } - return tower; + return fd_tower_votes_delete( tower ); } static inline ulong -lockout_expiration_slot( fd_tower_vote_t const * vote ) { +expiration( fd_tower_vote_t const * vote ) { ulong lockout = 1UL << vote->conf; return vote->slot + lockout; } static inline ulong -simulate_vote( fd_tower_vote_t const * votes, ulong slot ) { - ulong cnt = fd_tower_votes_cnt( votes ); +simulate_vote( fd_tower_t const * tower, ulong slot ) { + ulong cnt = fd_tower_votes_cnt( tower ); while( cnt ) { /* Return early if we can't pop the top tower vote, even if votes below it are expired. */ - if( FD_LIKELY( lockout_expiration_slot( fd_tower_votes_peek_index_const( votes, cnt - 1 ) ) >= slot ) ) { + if( FD_LIKELY( expiration( fd_tower_votes_peek_index_const( tower, cnt - 1 ) ) >= slot ) ) { break; } cnt--; @@ -114,11 +90,14 @@ int fd_tower_lockout_check( fd_tower_t const * tower, fd_ghost_t const * ghost, ulong slot ) { + #if FD_TOWER_USE_HANDHOLDING + FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */ + #endif /* Simulate a vote to pop off all the votes that have been expired at the top of the tower. */ - ulong cnt = simulate_vote( tower->votes, slot ); + ulong cnt = simulate_vote( tower, slot ); /* By definition, all votes in the tower must be for the same fork, so check if the previous vote (ie. the last vote in the tower) is on @@ -130,16 +109,12 @@ fd_tower_lockout_check( fd_tower_t const * tower, FIXME discuss if it is safe to assume that? */ - fd_tower_vote_t const * prev_vote = fd_tower_votes_peek_index_const( tower->votes, cnt - 1 ); + fd_tower_vote_t const * vote = fd_tower_votes_peek_index_const( tower, cnt - 1 ); fd_ghost_node_t const * root = fd_ghost_root( ghost ); - int lockout_check = prev_vote->slot < root->slot || - fd_ghost_is_ancestor( ghost, prev_vote->slot, slot ); - FD_LOG_NOTICE(( "[fd_tower_lockout_check] ok? %d. top: (slot: %lu, conf: %lu). switch: %lu.", - lockout_check, - prev_vote->slot, - prev_vote->conf, - slot )); + int lockout_check = vote->slot < root->slot || + fd_ghost_is_ancestor( ghost, vote->slot, slot ); + FD_LOG_NOTICE(( "[fd_tower_lockout_check] ok? %d. top: (slot: %lu, conf: %lu). switch: %lu.", lockout_check, vote->slot, vote->conf, slot )); return lockout_check; } @@ -148,13 +123,16 @@ fd_tower_switch_check( fd_tower_t const * tower, fd_epoch_t const * epoch, fd_ghost_t const * ghost, ulong slot ) { + #if FD_TOWER_USE_HANDHOLDING + FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */ + #endif - fd_tower_vote_t const * latest_vote = fd_tower_votes_peek_tail_const( tower->votes ); - fd_ghost_node_t const * root = fd_ghost_root( ghost ); + fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower ); + fd_ghost_node_t const * root = fd_ghost_root( ghost ); - if( FD_UNLIKELY( latest_vote->slot < root->slot ) ) { + if( FD_UNLIKELY( vote->slot < root->slot ) ) { - /* It is possible our latest vote slot precedes our ghost root. This + /* It is possible our last vote slot precedes our ghost root. This can happen, for example, when we restart from a snapshot and set the ghost root to the snapshot slot (we won't have an ancestry before the snapshot slot.) @@ -187,34 +165,34 @@ fd_tower_switch_check( fd_tower_t const * tower, */ #if FD_TOWER_USE_HANDHOLDING - FD_TEST( !fd_ghost_is_ancestor( ghost, latest_vote->slot, slot ) ); + FD_TEST( !fd_ghost_is_ancestor( ghost, vote->slot, slot ) ); #endif fd_ghost_node_map_t const * node_map = fd_ghost_node_map_const( ghost ); fd_ghost_node_t const * node_pool = fd_ghost_node_pool_const( ghost ); - fd_ghost_node_t const * gca = fd_ghost_gca( ghost, latest_vote->slot, slot ); + fd_ghost_node_t const * gca = fd_ghost_gca( ghost, vote->slot, slot ); ulong gca_idx = fd_ghost_node_map_idx_query_const( node_map, &gca->slot, ULONG_MAX, node_pool ); /* gca_child is our latest_vote slot's ancestor that is also a direct child of GCA. So we do not count it towards the stake of the different forks. */ - fd_ghost_node_t const * gca_child = fd_ghost_query( ghost, latest_vote->slot ); + fd_ghost_node_t const * gca_child = fd_ghost_query( ghost, vote->slot ); while( gca_child->parent_idx != gca_idx ) { gca_child = fd_ghost_node_pool_ele_const( node_pool, gca_child->parent_idx ); } ulong switch_stake = 0; fd_ghost_node_t const * child = fd_ghost_child( ghost, gca ); - while ( FD_LIKELY( child ) ) { - if ( FD_LIKELY ( child != gca_child ) ) { + while( FD_LIKELY( child ) ) { + if( FD_LIKELY( child != gca_child ) ) { switch_stake += child->weight; } child = fd_ghost_node_pool_ele_const( node_pool, child->sibling_idx ); } double switch_pct = (double)switch_stake / (double)epoch->total_stake; - FD_LOG_DEBUG(( "[%s] ok? %d. top: %lu. switch: %lu. switch stake: %.0lf%%.", __func__, switch_pct > SWITCH_PCT, fd_tower_votes_peek_tail_const( tower->votes )->slot, slot, switch_pct * 100.0 )); + FD_LOG_DEBUG(( "[%s] ok? %d. top: %lu. switch: %lu. switch stake: %.0lf%%.", __func__, switch_pct > SWITCH_PCT, fd_tower_votes_peek_tail_const( tower )->slot, slot, switch_pct * 100.0 )); return switch_pct > SWITCH_PCT; } @@ -228,7 +206,7 @@ fd_tower_threshold_check( fd_tower_t const * tower, /* First, simulate a vote, popping off everything that would be expired by voting for the current slot. */ - ulong cnt = simulate_vote( tower->votes, slot ); + ulong cnt = simulate_vote( tower, slot ); /* Return early if our tower is not at least THRESHOLD_DEPTH deep after simulating. */ @@ -240,7 +218,7 @@ fd_tower_threshold_check( fd_tower_t const * tower, which is not accounted for by `cnt`, so subtracting THRESHOLD_DEPTH will conveniently index the threshold vote. */ - ulong threshold_slot = fd_tower_votes_peek_index( tower->votes, cnt - THRESHOLD_DEPTH )->slot; + ulong threshold_slot = fd_tower_votes_peek_index_const( tower, cnt - THRESHOLD_DEPTH )->slot; /* Track the amount of stake that has vote slot >= threshold_slot. */ @@ -257,22 +235,22 @@ fd_tower_threshold_check( fd_tower_t const * tower, /* Convert the landed_votes into tower's vote_slots interface. */ FD_SCRATCH_SCOPE_BEGIN { void * mem = fd_scratch_alloc( fd_tower_align(), fd_tower_footprint() ); - fd_tower_t * their_tower = fd_tower_join( fd_tower_new( mem ) ); - fd_tower_from_vote_acc( their_tower, funk, txn, &voter->rec ); + fd_tower_t * voter_tower = fd_tower_join( fd_tower_new( mem ) ); + fd_tower_from_vote_acc( voter_tower, funk, txn, &voter->rec ); /* If this voter has not voted, continue. */ - if( FD_UNLIKELY( fd_tower_votes_empty( their_tower->votes ) ) ) continue; + if( FD_UNLIKELY( fd_tower_votes_empty( voter_tower ) ) ) continue; - ulong cnt = simulate_vote( their_tower->votes, slot ); + ulong cnt = simulate_vote( voter_tower, slot ); /* Continue if their tower is empty after simulating. */ if( FD_UNLIKELY( !cnt ) ) continue; - /* Get their latest vote slot. */ + /* Get their latest vote. */ - fd_tower_vote_t const * vote_slot = fd_tower_votes_peek_index( their_tower->votes, cnt - 1 ); + fd_tower_vote_t const * vote = fd_tower_votes_peek_index( voter_tower, cnt - 1 ); /* Count their stake towards the threshold check if their latest vote slot >= our threshold slot. @@ -285,14 +263,14 @@ fd_tower_threshold_check( fd_tower_t const * tower, know that vote must be for the threshold slot itself or one of threshold slot's descendants. */ - if( FD_LIKELY( vote_slot->slot >= threshold_slot ) ) { + if( FD_LIKELY( vote->slot >= threshold_slot ) ) { threshold_stake += voter->stake; } } FD_SCRATCH_SCOPE_END; } double threshold_pct = (double)threshold_stake / (double)epoch->total_stake; - FD_LOG_NOTICE(( "[%s] ok? %d. top: %lu. threshold: %lu. stake: %.0lf%%.", __func__, threshold_pct > THRESHOLD_PCT, fd_tower_votes_peek_tail_const( tower->votes )->slot, threshold_slot, threshold_pct * 100.0 )); + FD_LOG_NOTICE(( "[%s] ok? %d. top: %lu. threshold: %lu. stake: %.0lf%%.", __func__, threshold_pct > THRESHOLD_PCT, fd_tower_votes_peek_tail_const( tower )->slot, threshold_slot, threshold_pct * 100.0 )); return threshold_pct > THRESHOLD_PCT; } @@ -301,7 +279,7 @@ fd_tower_reset_slot( fd_tower_t const * tower, fd_epoch_t const * epoch, fd_ghost_t const * ghost ) { - fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower->votes ); + fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower ); fd_ghost_node_t const * root = fd_ghost_root( ghost ); fd_ghost_node_t const * head = fd_ghost_head( ghost, root ); @@ -324,7 +302,6 @@ fd_tower_reset_slot( fd_tower_t const * tower, #if FD_TOWER_USE_HANDHOLDING if( FD_UNLIKELY( !vote_node ) ) { fd_ghost_print( ghost, epoch, root ); - fd_tower_print( tower ); FD_LOG_ERR(( "[%s] invariant violation: unable to find last tower vote slot %lu in ghost.", __func__, vote->slot )); } #endif @@ -342,7 +319,7 @@ fd_tower_vote_slot( fd_tower_t * tower, fd_funk_txn_t const * txn, fd_ghost_t const * ghost ) { - fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower->votes ); + fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower ); fd_ghost_node_t const * root = fd_ghost_root( ghost ); fd_ghost_node_t const * head = fd_ghost_head( ghost, root ); @@ -387,55 +364,43 @@ fd_tower_vote_slot( fd_tower_t * tower, return FD_SLOT_NULL; } -void -fd_tower_vote( fd_tower_t const * tower, ulong slot ) { +ulong +fd_tower_vote( fd_tower_t * tower, ulong slot ) { FD_LOG_DEBUG(( "[%s] voting for slot %lu", __func__, slot )); -#if FD_TOWER_USE_HANDHOLDING - - /* Check we're not voting for the exact same slot as our latest tower - vote. This can happen when there are forks. */ - - fd_tower_vote_t * latest_vote = fd_tower_votes_peek_tail( tower->votes ); - if( FD_UNLIKELY( latest_vote && latest_vote->slot == slot ) ) { - FD_LOG_WARNING(( "[%s] already voted for slot %lu", __func__, slot )); - return; - } - - /* Check we aren't voting for a slot earlier than the last tower - vote. This should not happen and indicates a bug, because on the - same vote fork the slot should be monotonically non-decreasing. */ - - for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower->votes ); - !fd_tower_votes_iter_done_rev( tower->votes, iter ); - iter = fd_tower_votes_iter_prev( tower->votes, iter ) ) { - fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower->votes, iter ); - if( FD_UNLIKELY( slot == vote->slot ) ) { - fd_tower_print( tower ); - FD_LOG_ERR(( "[%s] double-voting for old slot %lu (new vote: %lu)", __func__, slot, vote->slot )); - } - } - -#endif + #if FD_TOWER_USE_HANDHOLDING + fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower ); + if( FD_UNLIKELY( vote && slot < vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu < vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/ + #endif /* Use simulate_vote to determine how many expired votes to pop. */ - ulong cnt = simulate_vote( tower->votes, slot ); + ulong cnt = simulate_vote( tower, slot ); /* Pop everything that got expired. */ - while( fd_tower_votes_cnt( tower->votes ) > cnt ) { - fd_tower_votes_pop_tail( tower->votes ); + while( fd_tower_votes_cnt( tower ) > cnt ) { + fd_tower_votes_pop_tail( tower ); + } + + /* If the tower is still full after expiring, then pop and return the + bottom vote slot as the new root because this vote has incremented + it to max lockout. Otherwise this is a no-op and there is no new + root (FD_SLOT_NULL). */ + + ulong root = FD_SLOT_NULL; + if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { /* optimize for full tower */ + root = fd_tower_votes_pop_head( tower ).slot; } /* Increment confirmations (double lockouts) for consecutive confirmations in prior votes. */ ulong prev_conf = 0; - for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower->votes ); - !fd_tower_votes_iter_done_rev( tower->votes, iter ); - iter = fd_tower_votes_iter_prev( tower->votes, iter ) ) { - fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower->votes, iter ); + for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower ); + !fd_tower_votes_iter_done_rev( tower, iter ); + iter = fd_tower_votes_iter_prev( tower, iter ) ) { + fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower, iter ); if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) { break; } @@ -444,12 +409,20 @@ fd_tower_vote( fd_tower_t const * tower, ulong slot ) { /* Add the new vote to the tower. */ - fd_tower_votes_push_tail( tower->votes, (fd_tower_vote_t){ .slot = slot, .conf = 1 } ); + fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = slot, .conf = 1 } ); + + /* Return the new root (FD_SLOT_NULL if there is none). */ + + return root; } ulong fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) { - return simulate_vote( tower->votes, slot ); + #if FD_TOWER_USE_HANDHOLDING + FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */ + #endif + + return simulate_vote( tower, slot ); } void @@ -457,9 +430,8 @@ fd_tower_from_vote_acc( fd_tower_t * tower, fd_funk_t * funk, fd_funk_txn_t const * txn, fd_funk_rec_key_t const * vote_acc ) { -#if FD_TOWER_USE_HANDHOLDING - if( FD_UNLIKELY( fd_tower_votes_cnt( tower->votes ) ) ) FD_LOG_ERR(( "[%s] cannot write to non-empty tower", __func__ )); - if( FD_UNLIKELY( tower->root != 0 && tower->root != FD_SLOT_NULL ) ) FD_LOG_ERR(( "[%s] cannot write to tower with a root", __func__ )); + #if FD_TOWER_USE_HANDHOLDING + if( FD_UNLIKELY( !fd_tower_votes_empty( tower ) ) ) FD_LOG_ERR(( "[%s] cannot write to non-empty tower", __func__ )); #endif fd_voter_state_t const * state = fd_voter_state( funk, txn, vote_acc ); @@ -473,13 +445,13 @@ fd_tower_from_vote_acc( fd_tower_t * tower, } else { memcpy( (uchar *)&vote, (uchar *)&state->tower.votes[i] + sizeof(uchar) /* latency */, vote_sz ); } - fd_tower_votes_push_tail( tower->votes, vote ); + fd_tower_votes_push_tail( tower, vote ); } - tower->root = fd_voter_state_root( state ); } void fd_tower_to_vote_txn( fd_tower_t const * tower, + ulong root, fd_hash_t const * bank_hash, fd_hash_t const * recent_blockhash, fd_pubkey_t const * validator_identity, @@ -489,20 +461,20 @@ fd_tower_to_vote_txn( fd_tower_t const * tower, fd_compact_vote_state_update_t tower_sync[1] = { 0 }; - tower_sync->root = tower->root; + tower_sync->root = root; long ts = fd_log_wallclock(); tower_sync->has_timestamp = 1; tower_sync->timestamp = ts; - tower_sync->lockouts_len = (ushort)fd_tower_votes_cnt( tower->votes ); + tower_sync->lockouts_len = (ushort)fd_tower_votes_cnt( tower ); tower_sync->lockouts = (fd_lockout_offset_t *)fd_scratch_alloc( alignof(fd_lockout_offset_t),tower_sync->lockouts_len * sizeof(fd_lockout_offset_t) ); ulong i = 0UL; ulong curr_slot = tower_sync->root; - for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower->votes ); - !fd_tower_votes_iter_done( tower->votes, iter ); - iter = fd_tower_votes_iter_next( tower->votes, iter ) ) { - fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower->votes, iter ); + for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower ); + !fd_tower_votes_iter_done( tower, iter ); + iter = fd_tower_votes_iter_next( tower, iter ) ) { + fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter ); FD_TEST( vote->slot >= tower_sync->root ); ulong offset = vote->slot - curr_slot; curr_slot = vote->slot; @@ -584,19 +556,36 @@ fd_tower_to_vote_txn( fd_tower_t const * tower, vote_txn->payload_sz = fd_txn_add_instr( txn_meta_out, txn_out, program_id, ix_accs, 2, vote_ix_buf, vote_ix_size ); } +int +fd_tower_verify( fd_tower_t const * tower ) { + fd_tower_vote_t const * prev = NULL; + for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower ); + !fd_tower_votes_iter_done( tower, iter ); + iter = fd_tower_votes_iter_next( tower, iter ) ) { + fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter ); + if( FD_LIKELY( prev && !( vote->slot < prev->slot && vote->conf < prev->conf ) ) ) { + FD_LOG_WARNING(( "[%s] invariant violation: vote %lu %lu. prev %lu %lu", __func__, vote->slot, vote->conf, prev->slot, prev->conf )); + return -1; + } + prev = vote; + } + return 0; +} + #include -static void -print( fd_tower_vote_t * tower_votes, ulong root ) { +void +fd_tower_print( fd_tower_t const * tower, ulong root ) { + FD_LOG_NOTICE( ( "\n\n[Tower]" ) ); ulong max_slot = 0; /* Determine spacing. */ - for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower_votes ); - !fd_tower_votes_iter_done_rev( tower_votes, iter ); - iter = fd_tower_votes_iter_prev( tower_votes, iter ) ) { + for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower ); + !fd_tower_votes_iter_done_rev( tower, iter ); + iter = fd_tower_votes_iter_prev( tower, iter ) ) { - max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower_votes, iter )->slot ); + max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot ); } /* Calculate the number of digits in the maximum slot value. */ @@ -625,20 +614,14 @@ print( fd_tower_vote_t * tower_votes, ulong root ) { /* Print each record in the table */ - for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower_votes ); - !fd_tower_votes_iter_done_rev( tower_votes, iter ); - iter = fd_tower_votes_iter_prev( tower_votes, iter ) ) { + for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower ); + !fd_tower_votes_iter_done_rev( tower, iter ); + iter = fd_tower_votes_iter_prev( tower, iter ) ) { - fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower_votes, iter ); + fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter ); printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf ); - max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower_votes, iter )->slot ); + max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot ); } printf( "%*lu | root\n", digit_cnt, root ); printf( "\n" ); } - -void -fd_tower_print( fd_tower_t const * tower ) { - FD_LOG_NOTICE( ( "\n\n[Tower]" ) ); - print( tower->votes, tower->root ); -} diff --git a/src/choreo/tower/fd_tower.h b/src/choreo/tower/fd_tower.h index 646890b37c..e8cbcdd49b 100644 --- a/src/choreo/tower/fd_tower.h +++ b/src/choreo/tower/fd_tower.h @@ -436,7 +436,7 @@ #define FD_TOWER_USE_HANDHOLDING 1 #endif -#define FD_TOWER_VOTE_MAX (32UL) +#define FD_TOWER_VOTE_MAX (31UL) struct fd_tower_vote { ulong slot; /* vote slot */ @@ -449,51 +449,44 @@ typedef struct fd_tower_vote fd_tower_vote_t; #define DEQUE_MAX FD_TOWER_VOTE_MAX #include "../../util/tmpl/fd_deque.c" -/* fd_tower implements the TowerBFT algorithm and related consensus - rules. */ +/* fd_tower is a representation of a validator's "vote tower" (described + in detail in the preamble at the top of this file). The votes in the + tower are stored in an fd_deque ordered from lowest to highest vote + slot (highest to lowest confirmation count) relative to the head and + tail . There can be at most 31 votes in the tower. This invariant + is upheld with every call to `fd_tower_vote`. -struct __attribute__((aligned(128UL))) fd_tower { + The definition of `fd_tower_t` is a simple typedef alias for + `fd_tower_vote_t` and is a transparent wrapper around the vote deque. + Relatedly, the tower API takes a local pointer to the first vote in + the deque (the result of `fd_deque_join`) as a parameter in all its + function signatures. */ - /* Owned memory */ +typedef fd_tower_vote_t fd_tower_t; - /* The votes currently in the tower, ordered from latest to earliest - vote slot (lowest to highest confirmation count). */ +/* FD_TOWER_{ALIGN,FOOTPRINT} specify the alignment and footprint needed + for tower. ALIGN is double x86 cache line to mitigate various kinds + of false sharing (eg. ACLPF adjacent cache line prefetch). FOOTPRINT + is the size of fd_deque including the private header's start and end + and an exact multiple of ALIGN. These are provided to facilitate + compile time tower declarations. */ - fd_tower_vote_t * votes; - - /* The root is the most recent vote in the tower to reach max lockout - (ie. confirmation count 32). It is no longer present in the tower - votes themselves. */ - - ulong root; /* FIXME wire with fseq */ - - /* smr is a non-NULL pointer to an fseq that always contains the - highest observed smr. This value is initialized by replay tile. - - Do not read or modify outside the fseq API. */ - - ulong * smr; -}; -typedef struct fd_tower fd_tower_t; +#define FD_TOWER_ALIGN (128UL) +#define FD_TOWER_FOOTPRINT (512UL) +FD_STATIC_ASSERT( FD_TOWER_FOOTPRINT==sizeof(fd_tower_votes_private_t), FD_TOWER_FOOTPRINT ); /* fd_tower_{align,footprint} return the required alignment and - footprint of a memory region suitable for use as tower. align is - double cache line to mitigate false sharing. */ + footprint of a memory region suitable for use as a tower. align + returns FD_TOWER_ALIGN. footprint returns FD_TOWER_FOOTPRINT. */ FD_FN_CONST static inline ulong fd_tower_align( void ) { - return alignof(fd_tower_t); + return FD_TOWER_ALIGN; } FD_FN_CONST static inline ulong fd_tower_footprint( void ) { - return FD_LAYOUT_FINI( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_INIT, - alignof(fd_tower_t), sizeof(fd_tower_t) ), - fd_tower_votes_align(), fd_tower_votes_footprint() ), - alignof(fd_tower_t) ); + return FD_TOWER_FOOTPRINT; } /* fd_tower_new formats an unused memory region for use as a tower. mem @@ -517,7 +510,7 @@ fd_tower_join( void * tower ); details). Reasons for failure include tower is NULL. */ void * -fd_tower_leave( fd_tower_t const * tower ); +fd_tower_leave( fd_tower_t * tower ); /* fd_tower_delete unformats a memory region used as a tower. Assumes only the local process is joined to the region. Returns a pointer to @@ -530,7 +523,7 @@ fd_tower_delete( void * tower ); /* fd_tower_lockout_check checks if we are locked out from voting for `slot`. Returns 1 if we can vote for `slot` without violating - lockout, 0 otherwise. + lockout, 0 otherwise. Assumes tower is non-empty. After voting for a slot n, we are locked out for 2^k slots, where k is the confirmation count of that vote. Once locked out, we cannot @@ -597,7 +590,7 @@ fd_tower_lockout_check( fd_tower_t const * tower, ulong slot ); /* fd_tower_switch_check checks if we can switch to `fork`. Returns 1 - if we can switch, 0 otherwise. + if we can switch, 0 otherwise. Assumes tower is non-empty. There are two forks of interest: our last vote fork ("vote fork") and the fork we want to switch to ("switch fork"). The switch fork is @@ -713,50 +706,31 @@ fd_tower_vote_slot( fd_tower_t * tower, /* fd_tower_simulate_vote simulates a vote on the vote tower for slot, returning the new height (cnt) for all the votes that would have been - popped. */ + popped. Assumes tower is non-empty. */ ulong fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ); -/* fd_tower_vote votes for slot. Assumes caller has already performed - all the tower checks to ensure this is a valid vote. */ - -void -fd_tower_vote( fd_tower_t const * tower, ulong slot ); - -/* fd_tower_is_max_lockout returns 1 if the bottom vote of the tower has - reached max lockout, 0 otherwise. Max lockout is equivalent to 1 << - FD_TOWER_VOTE_MAX (equivalently, confirmation count is - FD_TOWER_VOTE_MAX). So if the tower is at height FD_TOWER_VOTE_MAX, - then the bottom vote has reached max lockout. */ +/* Operations */ -static inline int -fd_tower_is_max_lockout( fd_tower_t const * tower ) { - return fd_tower_votes_cnt( tower->votes ) == FD_TOWER_VOTE_MAX; -} - -/* fd_tower_publish publishes the tower. Returns the new root. Assumes - caller has already checked that tower is at max lockout (see - fd_tower_is_max_lockout). +/* fd_tower_vote votes for slot. Assumes caller has already performed + the relevant tower checks (lockout_check, etc.) to ensure it is valid + to vote for `slot`. Returns a new root if this vote results in the + lowest vote slot in the tower reaching max lockout. The lowest vote + will also be popped from the tower. - smr is a non-NULL pointer to an fseq that always contains the highest - observed smr. + Max lockout is equivalent to 1 << FD_TOWER_VOTE_MAX + 1 (which + implies confirmation count is FD_TOWER_VOTE_MAX + 1). As a result, + fd_tower_vote also maintains the invariant that the tower contains at + most FD_TOWER_VOTE_MAX votes, because (in addition to vote expiry) + there will always be a pop before reaching FD_TOWER_VOTE_MAX + 1. */ - IMPORTANT! Caller should not read or modify this value outside the - fseq API. */ +ulong +fd_tower_vote( fd_tower_t * tower, ulong slot ); -static inline ulong -fd_tower_publish( fd_tower_t * tower ) { - #if FD_TOWER_USE_HANDHOLDING - FD_TEST( fd_tower_is_max_lockout( tower ) ); - #endif +/* Misc */ - ulong root = fd_tower_votes_pop_head( tower->votes ).slot; - tower->root = root; - return root; -} - -/* fd_voter_from_vote_acc writes the saved tower inside `state` to the +/* fd_tower_from_vote_acc writes the saved tower inside `state` to the caller-provided `tower`. Assumes `tower` is a valid join of an fd_tower that is currently empty. */ @@ -771,6 +745,7 @@ fd_tower_from_vote_acc( fd_tower_t * tower, void fd_tower_to_vote_txn( fd_tower_t const * tower, + ulong root, fd_hash_t const * bank_hash, fd_hash_t const * recent_blockhash, fd_pubkey_t const * validator_identity, @@ -778,6 +753,14 @@ fd_tower_to_vote_txn( fd_tower_t const * tower, fd_pubkey_t const * vote_acc, fd_txn_p_t * vote_txn ); +/* fd_tower_verify checks the tower is in a valid state. The cnt should + be < FD_TOWER_VOTE_MAX, the vote slots and confirmation counts in the + tower should be monotonically increasing, and the root should be < + the bottom vote. */ + +int +fd_tower_verify( fd_tower_t const * tower ); + /* fd_tower_print pretty-prints tower as a formatted table. Sample output: @@ -794,6 +777,6 @@ fd_tower_to_vote_txn( fd_tower_t const * tower, */ void -fd_tower_print( fd_tower_t const * tower ); +fd_tower_print( fd_tower_t const * tower, ulong root ); #endif /* HEADER_fd_src_choreo_tower_fd_tower_h */ diff --git a/src/choreo/tower/test_tower.c b/src/choreo/tower/test_tower.c index b055e1707a..77ab07e38e 100644 --- a/src/choreo/tower/test_tower.c +++ b/src/choreo/tower/test_tower.c @@ -1,10 +1,10 @@ #include "fd_tower.h" -uchar mem[16384] __attribute__((aligned(alignof(fd_tower_t)))); +uchar scratch[ FD_TOWER_FOOTPRINT ] __attribute__((aligned(FD_TOWER_ALIGN))); void test_tower_vote( void ) { - fd_tower_t * tower = fd_tower_join( fd_tower_new( mem ) ); + fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) ); FD_TEST( tower ); /* Add some votes to the tower @@ -19,11 +19,11 @@ test_tower_vote( void ) { for( ulong i = 0; i < 31; i++ ) { fd_tower_vote( tower, i ); - FD_TEST( fd_tower_votes_cnt( tower->votes ) == i + 1 ); + FD_TEST( fd_tower_votes_cnt( tower ) == i + 1 ); } for( ulong i = 0; i < 31; i++ ) { fd_tower_vote_t expected_vote = { .slot = i, .conf = 31 - i }; - fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower->votes, i ); + fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, i ); FD_TEST( expected_vote.slot == actual_vote->slot ); FD_TEST( expected_vote.conf == actual_vote->conf ); } @@ -44,7 +44,7 @@ test_tower_vote( void ) { fd_tower_vote( tower, new_vote_expiry ); for( ulong i = 0; i < 30; i++ ) { fd_tower_vote_t expected_vote = { .slot = i, .conf = 31 - i }; - fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower->votes, i ); + fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, i ); FD_TEST( expected_vote.slot == actual_vote->slot ); FD_TEST( expected_vote.conf == actual_vote->conf ); } @@ -52,28 +52,21 @@ test_tower_vote( void ) { /* Check new vote */ fd_tower_vote_t expected_vote = { .slot = new_vote_expiry, .conf = 1 }; - fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower->votes, 30 ); + fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, 30 ); FD_TEST( expected_vote.slot == actual_vote->slot ); FD_TEST( expected_vote.conf == actual_vote->conf ); /* CASE 2: NEW VOTE WHICH PRODUCES NEW ROOT */ ulong new_vote_root = 34; - fd_tower_vote( tower, new_vote_root ); - FD_TEST( fd_tower_is_max_lockout( tower ) ); - - /* Check root */ - - ulong expected_root = 0; - ulong actual_root = fd_tower_publish( tower ); - FD_TEST( actual_root == expected_root ); + FD_TEST( fd_tower_vote( tower, new_vote_root ) == 0 ); /* Check all existing votes were repositioned one index lower and one confirmation higher. */ for( ulong i = 0; i < 29 /* one of the original slots was rooted */; i++ ) { fd_tower_vote_t expected_vote = { .slot = i + 1, .conf = 31 - i }; - fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower->votes, i ); + fd_tower_vote_t * actual_vote = fd_tower_votes_peek_index( tower, i ); FD_TEST( expected_vote.slot == actual_vote->slot ); FD_TEST( expected_vote.conf == actual_vote->conf ); } @@ -81,7 +74,7 @@ test_tower_vote( void ) { /* Check new vote in the tower. */ fd_tower_vote_t expected_vote_root = { .slot = new_vote_root, .conf = 1 }; - fd_tower_vote_t * actual_vote_root = fd_tower_votes_peek_index( tower->votes, 30 ); + fd_tower_vote_t * actual_vote_root = fd_tower_votes_peek_index( tower, 30 ); FD_TEST( expected_vote_root.slot == actual_vote_root->slot ); FD_TEST( expected_vote_root.conf == actual_vote_root->conf ); diff --git a/src/disco/restart/fd_restart.c b/src/disco/restart/fd_restart.c index c9bb7ac257..14264c82eb 100644 --- a/src/disco/restart/fd_restart.c +++ b/src/disco/restart/fd_restart.c @@ -438,10 +438,11 @@ fd_restart_init( fd_restart_t * restart, void fd_restart_tower_checkpt( fd_hash_t const * vote_bank_hash, fd_tower_t * tower, + ulong root, int tower_checkpt_fileno ) { lseek( tower_checkpt_fileno, 0, SEEK_SET ); ulong wsz, total_wsz = 0; - ulong slots_cnt = fd_tower_votes_cnt( tower->votes )+1; + ulong slots_cnt = fd_tower_votes_cnt( tower )+1; fd_io_write( tower_checkpt_fileno, vote_bank_hash, sizeof(fd_hash_t), sizeof(fd_hash_t), &wsz ); if( FD_UNLIKELY( wsz!=sizeof(fd_hash_t) ) ) goto checkpt_finish; @@ -449,14 +450,14 @@ fd_restart_tower_checkpt( fd_hash_t const * vote_bank_hash, fd_io_write( tower_checkpt_fileno, &slots_cnt, sizeof(ulong), sizeof(ulong), &wsz ); if( FD_UNLIKELY( wsz!=sizeof(ulong) ) ) goto checkpt_finish; total_wsz += wsz; - fd_io_write( tower_checkpt_fileno, &tower->root, sizeof(ulong), sizeof(ulong), &wsz ); + fd_io_write( tower_checkpt_fileno, &root, sizeof(ulong), sizeof(ulong), &wsz ); if( FD_UNLIKELY( wsz!=sizeof(ulong) ) ) goto checkpt_finish; total_wsz += wsz; - for( fd_tower_votes_iter_t tower_iter = fd_tower_votes_iter_init( tower->votes ); - !fd_tower_votes_iter_done( tower->votes, tower_iter ); - tower_iter = fd_tower_votes_iter_next( tower->votes, tower_iter ) ) { - ulong slot = fd_tower_votes_iter_ele( tower->votes, tower_iter )->slot; + for( fd_tower_votes_iter_t tower_iter = fd_tower_votes_iter_init( tower ); + !fd_tower_votes_iter_done( tower, tower_iter ); + tower_iter = fd_tower_votes_iter_next( tower, tower_iter ) ) { + ulong slot = fd_tower_votes_iter_ele( tower, tower_iter )->slot; fd_io_write( tower_checkpt_fileno, &slot, sizeof(ulong), sizeof(ulong), &wsz ); if( FD_UNLIKELY( wsz!=sizeof(ulong) ) ) goto checkpt_finish; total_wsz += wsz; diff --git a/src/disco/restart/fd_restart.h b/src/disco/restart/fd_restart.h index 1adfcbe9ba..952b915506 100644 --- a/src/disco/restart/fd_restart.h +++ b/src/disco/restart/fd_restart.h @@ -176,6 +176,7 @@ fd_restart_convert_raw_bitmap_to_runlength( fd_gossip_restart_last_voted_fork_sl void fd_restart_tower_checkpt( fd_hash_t const * vote_bank_hash, fd_tower_t * tower, + ulong root, int tower_checkpt_fileno ); void