diff --git a/src/choreo/Local.mk b/src/choreo/Local.mk index c4fc47d240..5514c75ede 100644 --- a/src/choreo/Local.mk +++ b/src/choreo/Local.mk @@ -2,4 +2,5 @@ ifdef FD_HAS_INT128 $(call make-lib,fd_choreo) $(call add-hdrs,fd_choreo_base.h fd_choreo.h) $(call make-unit-test,test_choreo_base,test_choreo_base,fd_choreo fd_flamenco fd_ballet fd_util) +$(call make-unit-test,test_choreo,test_choreo,fd_choreo fd_flamenco fd_ballet fd_util fd_funk fd_tango) endif diff --git a/src/choreo/test_choreo.c b/src/choreo/test_choreo.c new file mode 100644 index 0000000000..7994a516e1 --- /dev/null +++ b/src/choreo/test_choreo.c @@ -0,0 +1,1851 @@ +#include "tower/fd_tower.h" +#include "forks/fd_forks.h" +#include "ghost/fd_ghost.h" +#include "../flamenco/runtime/program/fd_vote_program.h" +#include "../flamenco/runtime/program/fd_vote_program.c" +#include "../flamenco/runtime/fd_txn_account.c" +#include "../funk/fd_funk_filemap.h" +#include "../util/wksp/fd_wksp.h" + +struct voter { + fd_pubkey_t pubkey; + fd_pubkey_t identity; +}; +typedef struct voter voter_t; + +fd_tower_t * +make_tower(void * tower_mem, size_t n, ...) { + fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem ) ); + + va_list args; + va_start(args, n); + for(size_t i = 0; i < n; i++) { + if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { + fd_tower_votes_pop_head( tower ); + } + + ulong prev_conf = 0; + 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; + } + vote->conf++; + } + + ulong slot = va_arg(args, ulong); + fd_tower_votes_push_tail(tower, (fd_tower_vote_t){.slot = slot, .conf = 1}); + } + va_end(args); + + return tower; +} + +#define TOWER(tower_mem, ...) \ + make_tower(tower_mem, (sizeof((ulong[]){__VA_ARGS__})/sizeof(ulong)), __VA_ARGS__) + +void +init_vote_accounts( voter_t * voters, + ulong voter_cnt ) { + fd_rng_t rng_[1]; + fd_rng_t * rng = fd_rng_join( fd_rng_new( rng_, 1234, 0UL ) ); + for( ulong i = 0; i < voter_cnt; i++ ) { + voters[i].pubkey = (fd_pubkey_t){ .ul={ fd_rng_ulong( rng ) } }; + voters[i].identity = (fd_pubkey_t){ .ul={ fd_rng_ulong( rng ) } }; + } +} + +fd_funk_txn_t * +create_slot_funk_txn( ulong slot, + ulong parent, + fd_funk_t * funk ) { + + fd_funk_txn_t * funk_txn; + fd_funk_txn_t * parent_funk_txn; + + if( parent==ULONG_MAX ) { + parent_funk_txn = NULL; + } else { + fd_funk_txn_xid_t xid; + xid.ul[0] = xid.ul[1] = parent; + fd_funk_txn_map_query_t query[1]; + fd_funk_txn_map_t txn_map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) ); + FD_TEST( fd_funk_txn_map_query_try( &txn_map, &xid, NULL, query, 0 )==FD_MAP_SUCCESS ); + FD_TEST( query->ele ); + parent_funk_txn = query->ele; + } + + fd_funk_txn_xid_t xid; + xid.ul[0] = xid.ul[1] = slot; + funk_txn = fd_funk_txn_prepare( funk, parent_funk_txn, &xid, 1 ); + + FD_TEST( funk_txn ); + return funk_txn; +} + +fd_vote_state_versioned_t * +tower_to_vote_state( fd_wksp_t * wksp, fd_tower_t * tower, voter_t * voter ) { + void * vote_state_versioned_mem = fd_wksp_alloc_laddr( wksp, FD_VOTE_STATE_VERSIONED_ALIGN, FD_VOTE_STATE_V3_SZ, 46UL ); + fd_vote_state_versioned_t * vote_state_versioned = (fd_vote_state_versioned_t*)vote_state_versioned_mem; + + ulong tower_height = fd_tower_votes_cnt( tower ); + + ulong cnt = fd_ulong_max( tower_height, MAX_LOCKOUT_HISTORY ); + void * deque_mem = fd_wksp_alloc_laddr( wksp, deq_fd_landed_vote_t_align(), deq_fd_landed_vote_t_footprint( cnt ), 2UL ); + fd_landed_vote_t * landed_votes = deq_fd_landed_vote_t_join( deq_fd_landed_vote_t_new( deque_mem, deq_fd_landed_vote_t_footprint(cnt ) ) ); + + for( ulong i = 0; i < tower_height; i++ ) { + fd_landed_vote_t * elem = deq_fd_landed_vote_t_push_tail_nocopy( landed_votes ); + fd_landed_vote_new( elem ); + + elem->latency = 0; + elem->lockout.slot = fd_tower_votes_peek_index_const( tower, i )->slot; + elem->lockout.confirmation_count = (uint)fd_tower_votes_peek_index_const( tower, i )->conf; + } + + fd_vote_authorized_voters_t authorized_voters = { + .pool = NULL, + .treap = NULL + }; + fd_vote_state_t vote_state = { + .node_pubkey = voter->identity, + .authorized_voters = authorized_voters, + .commission = 0, + .authorized_withdrawer = voter->pubkey, + .prior_voters = (fd_vote_prior_voters_t) { + .idx = 31UL, + .is_empty = 1, + }, + .votes = landed_votes, + .root_slot = FD_SLOT_NULL, + .epoch_credits = NULL + }; + + vote_state_versioned->discriminant = fd_vote_state_versioned_enum_current; + vote_state_versioned->inner.current = vote_state; + + return vote_state_versioned; +} + +fd_funk_txn_t * +get_slot_funk_txn( fd_funk_t * funk, ulong slot ) { + fd_funk_txn_xid_t xid; + xid.ul[0] = xid.ul[1] = slot; + fd_funk_txn_map_query_t funk_txn_query[1]; + fd_funk_txn_map_t txn_map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) ); + FD_TEST( fd_funk_txn_map_query_try( &txn_map, &xid, NULL, funk_txn_query, 0 )==FD_MAP_SUCCESS ); + FD_TEST( funk_txn_query->ele ); + return funk_txn_query->ele; +} + +void +insert_vote_state_into_funk_txn( fd_funk_t * funk, + fd_funk_txn_t * funk_txn, + fd_pubkey_t * voter, + fd_vote_state_versioned_t * vote_state_versioned ) { + + fd_funk_rec_query_t funk_rec_query[1]; + fd_funk_rec_key_t key = fd_funk_acc_key( voter ); + fd_funk_rec_t const * rec = fd_funk_rec_query_try( funk, funk_txn, &key, funk_rec_query ); + + if( FD_UNLIKELY( true ) ) { + // Save the children pointers + uint saved_child_head = funk_txn->child_head_cidx; + uint saved_child_tail = funk_txn->child_tail_cidx; + + // Temporarily detach children + funk_txn->child_head_cidx = fd_funk_txn_cidx(FD_FUNK_TXN_IDX_NULL); + funk_txn->child_tail_cidx = fd_funk_txn_cidx(FD_FUNK_TXN_IDX_NULL); + + fd_wksp_t * funk_wksp = fd_funk_wksp( funk ); + fd_funk_rec_key_t key = fd_funk_acc_key( voter ); + fd_funk_rec_prepare_t prepare[1]; + fd_funk_rec_t * prepare_rec = fd_funk_rec_prepare( funk, funk_txn, &key, prepare, NULL ); + FD_TEST( prepare_rec ); + + fd_account_meta_t * meta = fd_funk_val_truncate( prepare_rec, sizeof(fd_account_meta_t)+FD_VOTE_STATE_V3_SZ, fd_funk_alloc( funk, funk_wksp ), funk_wksp, NULL ); + fd_account_meta_init( meta ); + meta->dlen = FD_VOTE_STATE_V3_SZ; + memcpy( meta->info.owner, &fd_solana_vote_program_id, sizeof(fd_pubkey_t) ); + fd_funk_rec_publish( prepare ); + memset((uchar *)meta + meta->hlen, 0, FD_VOTE_STATE_V3_SZ); + + // Restore the children pointers + funk_txn->child_head_cidx = saved_child_head; + funk_txn->child_tail_cidx = saved_child_tail; + + } + + rec = fd_funk_rec_query_try( funk, funk_txn, &key, funk_rec_query ); + + FD_TEST( rec ); + fd_account_meta_t const * account_meta = fd_funk_val_const( rec, fd_funk_wksp( funk ) ); + FD_TEST( account_meta ); + + fd_bincode_encode_ctx_t encode_ctx = { + .data = (uchar*)account_meta + account_meta->hlen, + .dataend = (uchar*)account_meta + account_meta->hlen + account_meta->dlen + }; + FD_TEST( fd_vote_state_versioned_encode( vote_state_versioned, &encode_ctx )==0 ); +} + +ulong +voter_vote_for_slot( fd_wksp_t * wksp, + fd_tower_t * tower, + fd_funk_t * funk, + ulong vote_slot, + ulong slot, + voter_t * voter ) { + // Update tower with vote + ulong root = fd_tower_vote( tower, vote_slot ); + + // Convert updated tower to vote state + fd_vote_state_versioned_t * vote_state_versioned = tower_to_vote_state( wksp, tower, voter ); + + // Get funk_txn for specified slot + fd_funk_txn_t * funk_txn = get_slot_funk_txn( funk, slot ); + + // Insert updated vote state into funk + insert_vote_state_into_funk_txn( funk, funk_txn, &voter->pubkey, vote_state_versioned ); + + return root; +} + +fd_epoch_t * +mock_epoch( fd_wksp_t * wksp, ulong voter_cnt, ulong * stakes, voter_t * voters ) { + ulong total_stake = 0; + void * epoch_mem = fd_wksp_alloc_laddr( wksp, fd_epoch_align(), fd_epoch_footprint( voter_cnt ), 1UL ); + FD_TEST( epoch_mem ); + fd_epoch_t * epoch = fd_epoch_join( fd_epoch_new( epoch_mem, voter_cnt ) ); + FD_TEST( epoch ); + for( ulong i = 0; i < voter_cnt; i++ ) { + fd_voter_t * voter = fd_epoch_voters_insert( fd_epoch_voters( epoch ), voters[i].pubkey ); + voter->rec = fd_funk_acc_key( &voters[i].pubkey ); + voter->stake = stakes[i]; + voter->replay_vote = FD_SLOT_NULL; + total_stake += stakes[i]; + } + epoch->total_stake = total_stake; + + return epoch; +} + +void +ghost_init( fd_ghost_t * ghost, ulong root, fd_funk_t * funk ) { + fd_ghost_init( ghost, root ); + create_slot_funk_txn( root, ULONG_MAX, funk ); +} + +void +ghost_insert( fd_ghost_t * ghost, ulong parent, ulong slot, fd_funk_t * funk ) { + fd_ghost_insert( ghost, parent, slot ); + create_slot_funk_txn( slot, parent, funk ); +} + +fd_forks_t * +mock_forks( fd_wksp_t * wksp, fd_funk_txn_t * funk_txn, ulong slot ) { + fd_exec_slot_ctx_t * slot_ctx = fd_wksp_alloc_laddr( wksp, FD_EXEC_SLOT_CTX_ALIGN, FD_EXEC_SLOT_CTX_FOOTPRINT, 7 ); + slot_ctx->slot_bank.slot = slot; + slot_ctx->funk_txn = funk_txn; + void * forks_mem = fd_wksp_alloc_laddr( wksp, fd_forks_align(), fd_forks_footprint( FD_BLOCK_MAX ), 4UL ); + fd_forks_t * forks = fd_forks_join( fd_forks_new( forks_mem, FD_BLOCK_MAX, 42UL ) ); + fd_forks_init( forks, slot_ctx ); + + FD_TEST( forks ); + return forks; +} + +#define INIT_FORKS( FRONTIER ) \ + do { \ + fd_funk_txn_xid_t xid; \ + xid.ul[0] = xid.ul[1] = FRONTIER; \ + fd_funk_txn_map_query_t query[1]; \ + fd_funk_txn_map_t txn_map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) ); \ + FD_TEST( fd_funk_txn_map_query_try( &txn_map, &xid, NULL, query, 0 )==FD_MAP_SUCCESS ); \ + FD_TEST( query->ele ); \ + forks = mock_forks( wksp, query->ele, FRONTIER ); \ + } while(0); \ + +#define ADD_FRONTIER_TO_FORKS( FRONTIER ) \ + do { \ + fd_funk_txn_xid_t xid; \ + xid.ul[0] = xid.ul[1] = FRONTIER; \ + fd_funk_txn_map_query_t query[1]; \ + fd_funk_txn_map_t txn_map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) ); \ + FD_TEST( fd_funk_txn_map_query_try( &txn_map, &xid, NULL, query, 0 )==FD_MAP_SUCCESS ); \ + FD_TEST( query->ele ); \ + fd_exec_slot_ctx_t * slot_ctx = fd_wksp_alloc_laddr( wksp, FD_EXEC_SLOT_CTX_ALIGN, FD_EXEC_SLOT_CTX_FOOTPRINT, 7 ); \ + slot_ctx->funk_txn = query->ele; \ + slot_ctx->slot_bank.slot = FRONTIER; \ + fd_fork_t * fork = fd_fork_pool_ele_acquire( forks->pool ); \ + fork->prev = fd_fork_pool_idx_null( forks->pool ); \ + fork->slot = FRONTIER; \ + fork->lock = 1; \ + fork->end_idx = UINT_MAX; \ + fork->slot_ctx = slot_ctx; \ + if( FD_UNLIKELY( !fd_fork_frontier_ele_insert( forks->frontier, fork, forks->pool ) ) ) { \ + FD_LOG_ERR( ( "Failed to insert frontier=%lu into forks", FRONTIER ) ); \ + } \ + } while(0); + +void +test_vote_simple( fd_wksp_t * wksp ) { + /**********************************************************************/ + /* Initialize funk */ + /**********************************************************************/ + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + /**********************************************************************/ + /* Initialize ghost tree */ + /**********************************************************************/ + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + + ghost_init( ghost, 331233200, funk ); + ghost_insert( ghost, 331233200, 331233201, funk ); + ghost_insert( ghost, 331233201, 331233202, funk ); + ghost_insert( ghost, 331233202, 331233203, funk ); + ghost_insert( ghost, 331233203, 331233204, funk ); + ghost_insert( ghost, 331233204, 331233205, funk ); + + /**********************************************************************/ + /* Initialize voters, stakes, epoch and funk_txns */ + /**********************************************************************/ + ulong voter_cnt = 5; + voter_t voters[voter_cnt]; + init_vote_accounts( voters, voter_cnt ); + + ulong stakes[] = {10000, 10000, 10000, 10000, 10000}; + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + + /**********************************************************************/ + /* Setup funk_txns for each slot with vote account funk records */ + /**********************************************************************/ + void * tower_mems[voter_cnt]; + fd_tower_t * towers[voter_cnt]; + for(ulong i = 0; i < voter_cnt; i++) { + tower_mems[i] = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + towers[i] = fd_tower_join( fd_tower_new( tower_mems[i] ) ); + } + + /**********************************************************************/ + /* Initialize landed votes per validator in funk */ + /**********************************************************************/ + + voter_vote_for_slot( wksp, towers[0], funk, 331233204, 331233205, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233204, 331233205, &voters[1] ); + voter_vote_for_slot( wksp, towers[2], funk, 331233204, 331233205, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233204, 331233205, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233204, 331233205, &voters[4] ); + + /**********************************************************************/ + /* Initialize tower, spad and setup forks */ + /**********************************************************************/ + void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + fd_tower_t * tower = TOWER( tower_mem, 331233201, 331233202, 331233203, 331233204 ); + + void * spad_mem = fd_wksp_alloc_laddr( wksp, fd_spad_align(), fd_spad_footprint( FD_TOWER_FOOTPRINT ), 5UL ); + fd_spad_t * spad = fd_spad_join( fd_spad_new( spad_mem, FD_TOWER_FOOTPRINT ) ); + + fd_forks_t * forks; + ulong frontier = 331233205; + INIT_FORKS( frontier ); + ulong curr_slot = frontier; + + fd_forks_update( forks, epoch, funk, ghost, curr_slot ); + + /**********************************************************************/ + /* Vote for slot 5 and check that the tower grows by 1 */ + /**********************************************************************/ + fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier, &curr_slot, NULL, forks->pool ); + ulong vote_slot = fd_tower_vote_slot( tower, epoch, funk, fork->slot_ctx->funk_txn, ghost, spad ); + FD_TEST( vote_slot==curr_slot ); + + ulong current_tower_height = fd_tower_votes_cnt( tower ); + fd_tower_vote( tower, vote_slot ); + FD_TEST( fd_tower_votes_cnt( tower )==current_tower_height+1 ); + + fd_funk_close_file( &funk_close_args ); +} + +/* slot 331233200 <-(no vote has landed yet) + | + slot 331233201 <-(all voters voted for 0) + / \ + (2 voters voted for 1)-> slot 331233202 | + slot 331233205 <-(3 voters voted for 1) + | + slot 331233206 <-(3 voters voted for 5) + + Suppose voter#0 voted for slot 331233202 after replaying slot 331233202; + When voter#0 replay slot 6, it should realize that fork 0-1-5-6 + (1) passes the lockout check because 5>2+2 + (2) passes the switch check because 60%>38% stake has voted for slot 5 +*/ +void +test_vote_switch_check( fd_wksp_t * wksp ) { + /**********************************************************************/ + /* Initialize funk */ + /**********************************************************************/ + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + /**********************************************************************/ + /* Initialize ghost tree */ + /*********************************************************************/ + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + + ghost_init( ghost, 331233200, funk ); + ghost_insert( ghost, 331233200, 331233201, funk ); + ghost_insert( ghost, 331233201, 331233202, funk ); + ghost_insert( ghost, 331233201, 331233205, funk ); + ghost_insert( ghost, 331233205, 331233206, funk ); + + /**********************************************************************/ + /* Initialize voters, stakes, epoch and funk_txns */ + /**********************************************************************/ + ulong voter_cnt = 5; + voter_t voters[voter_cnt]; + init_vote_accounts( voters, voter_cnt ); + + ulong stakes[] = {10000, 10000, 10000, 10000, 10000}; + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + + /**********************************************************************/ + /* Setup funk_txns for each slot with vote account funk records */ + /**********************************************************************/ + void * tower_mems[voter_cnt]; + fd_tower_t * towers[voter_cnt]; + for(ulong i = 0; i < voter_cnt; i++) { + tower_mems[i] = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + towers[i] = fd_tower_join( fd_tower_new( tower_mems[i] ) ); + } + + /**********************************************************************/ + /* Initialize landed votes per validator in funk */ + /**********************************************************************/ + voter_vote_for_slot( wksp, towers[0], funk, 331233200, 331233201, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233200, 331233201, &voters[1] ); + voter_vote_for_slot( wksp, towers[2], funk, 331233200, 331233201, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233200, 331233201, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233200, 331233201, &voters[4] ); + + voter_vote_for_slot( wksp, towers[0], funk, 331233201, 331233202, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233201, 331233202, &voters[1] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233201, 331233205, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233201, 331233205, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233201, 331233205, &voters[4] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233205, 331233206, &voters[2] ); + + /**********************************************************************/ + /* Initialize tower, spad and setup forks */ + /**********************************************************************/ + void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + fd_tower_t * tower = TOWER( tower_mem, 331233200, 331233201, 331233202 ); + + void * spad_mem = fd_wksp_alloc_laddr( wksp, fd_spad_align(), fd_spad_footprint( FD_TOWER_FOOTPRINT ), 5UL ); + fd_spad_t * spad = fd_spad_join( fd_spad_new( spad_mem, FD_TOWER_FOOTPRINT ) ); + + fd_forks_t * forks; + ulong frontier1 = 331233202; + ulong frontier2 = 331233206; + INIT_FORKS( frontier1 ); + ADD_FRONTIER_TO_FORKS( frontier2 ); + + fd_forks_update( forks, epoch, funk, ghost, frontier1 ); + fd_forks_update( forks, epoch, funk, ghost, frontier2 ); + + /**********************************************************************/ + /* Try to vote for slot 331233206 */ + /* We should NOT switch to a different fork */ + /**********************************************************************/ + ulong try_to_vote_slot = frontier2; + fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier, &try_to_vote_slot, NULL, forks->pool ); + // Validate fd_tower_switch_check returns 0 + FD_TEST( !fd_tower_switch_check( tower, epoch, ghost, fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot ) ); + ulong vote_slot = fd_tower_vote_slot( tower, epoch, funk, fork->slot_ctx->funk_txn, ghost, spad ); + + FD_TEST( vote_slot==ULONG_MAX ); + + /**********************************************************************/ + /* Give fork 331233205 enough stake */ + /* Try to vote for slot 331233206 */ + /* We should switch to a different fork */ + /**********************************************************************/ + // fd_tower_vote( towers[3], 331233205 ); + // fd_tower_vote( towers[4], 331233205 ); + + voter_vote_for_slot( wksp, towers[3], funk, 331233205, 331233206, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233205, 331233206, &voters[4] ); + + fd_forks_update( forks, epoch, funk, ghost, frontier2 ); + // Validate fd_tower_switch_check returns 1 + FD_TEST( fd_tower_switch_check( tower, epoch, ghost, fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot ) ); + vote_slot = fd_tower_vote_slot( tower, epoch, funk, fork->slot_ctx->funk_txn, ghost, spad ); + FD_TEST( vote_slot==frontier2 ); + FD_TEST( fd_tower_votes_cnt( tower )==3 ); + fd_tower_vote( tower, vote_slot ); + FD_TEST( fd_tower_votes_cnt( tower )==2 ); + + fd_funk_close_file( &funk_close_args ); +} + +/* / -- 331233206 + / -- 331233202 -- 331233203 + (all voters voted for 331233200)-> 331233200 -- 331233201 -- 331233205 + \ -- 331233204 -- 331233208 + + Consider 4 voters, each voting for 331233206, 331233203, 331233205 and 331233208. +*/ +void +test_vote_switch_check_4forks( fd_wksp_t * wksp ) { + /**********************************************************************/ + /* Initialize funk */ + /**********************************************************************/ + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + /**********************************************************************/ + /* Initialize ghost tree */ + /*********************************************************************/ + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + + ghost_init( ghost, 331233200, funk ); + ghost_insert( ghost, 331233200, 331233201, funk ); + ghost_insert( ghost, 331233201, 331233202, funk ); + ghost_insert( ghost, 331233202, 331233203, funk ); + ghost_insert( ghost, 331233201, 331233204, funk ); + ghost_insert( ghost, 331233201, 331233205, funk ); + ghost_insert( ghost, 331233202, 331233206, funk ); + ghost_insert( ghost, 331233204, 331233208, funk ); + + /**********************************************************************/ + /* Initialize voters, stakes, epoch and funk_txns */ + /**********************************************************************/ + ulong voter_cnt = 4; + voter_t voters[voter_cnt]; + init_vote_accounts( voters, voter_cnt ); + + ulong stakes[] = {10000, 10000, 10000, 20001}; + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + + /**********************************************************************/ + /* Setup funk_txns for each slot with vote account funk records */ + /**********************************************************************/ + void * tower_mems[voter_cnt]; + fd_tower_t * towers[voter_cnt]; + for(ulong i = 0; i < voter_cnt; i++) { + tower_mems[i] = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + towers[i] = fd_tower_join( fd_tower_new( tower_mems[i] ) ); + } + + /**********************************************************************/ + /* Initialize landed votes per validator in funk */ + /**********************************************************************/ + voter_vote_for_slot( wksp, towers[0], funk, 331233200, 331233201, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233200, 331233201, &voters[1] ); + voter_vote_for_slot( wksp, towers[2], funk, 331233200, 331233201, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233200, 331233201, &voters[3] ); + + voter_vote_for_slot( wksp, towers[0], funk, 331233202, 331233202, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233202, 331233202, &voters[1] ); + + voter_vote_for_slot( wksp, towers[0], funk, 331233206, 331233206, &voters[0] ); + + voter_vote_for_slot( wksp, towers[1], funk, 331233203, 331233203, &voters[1] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233205, 331233205, &voters[2] ); + + voter_vote_for_slot( wksp, towers[3], funk, 331233204, 331233204, &voters[3] ); + + voter_vote_for_slot( wksp, towers[3], funk, 331233208, 331233208, &voters[3] ); + + /**********************************************************************/ + /* Initialize tower, spad and setup forks */ + /**********************************************************************/ + void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + fd_tower_t * tower = TOWER( tower_mem, 331233200, 331233201, 331233202, 331233203 ); + + void * spad_mem = fd_wksp_alloc_laddr( wksp, fd_spad_align(), fd_spad_footprint( FD_TOWER_FOOTPRINT ), 5UL ); + fd_spad_t * spad = fd_spad_join( fd_spad_new( spad_mem, FD_TOWER_FOOTPRINT ) ); + + fd_forks_t * forks; + ulong frontier1 = 331233206; + ulong frontier2 = 331233203; + ulong frontier3 = 331233205; + ulong frontier4 = 331233208; + INIT_FORKS( frontier1 ); + ADD_FRONTIER_TO_FORKS( frontier2 ); + ADD_FRONTIER_TO_FORKS( frontier3 ); + ADD_FRONTIER_TO_FORKS( frontier4 ); + + fd_forks_update( forks, epoch, funk, ghost, frontier1 ); + fd_forks_update( forks, epoch, funk, ghost, frontier2 ); + fd_forks_update( forks, epoch, funk, ghost, frontier3 ); + fd_forks_update( forks, epoch, funk, ghost, frontier4 ); + + /**********************************************************************/ + /* Try to switch from 331233203 to 331233208 */ + /* Switch rule allows switching from 331233203 to 331233208 */ + /* GCA(3, 8)=1 and weight(4)+weight(5)=40%%+20%%>38%% */ + /**********************************************************************/ + ulong try_to_vote_slot = frontier4; + FD_TEST( try_to_vote_slot==fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot ); + fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier, &try_to_vote_slot, NULL, forks->pool ); + ulong vote_slot = fd_tower_vote_slot( tower, epoch, funk, fork->slot_ctx->funk_txn, ghost, spad ); + + FD_TEST( vote_slot==frontier4 ); + + fd_funk_close_file( &funk_close_args ); +} + +/* slot 331233200 <-(no vote has landed yet) + | + slot 331233201 <-(all voters voted for 0) + / \ + (2 voters voted for 1)-> slot 331233202 | + slot 331233203 <-(3 voters voted for 1) + | + slot 331233204 <-(3 voters voted for 5) + | + slot 331233205 <-(3 voters voted for 6 + + Suppose voter#0 voted for slot 331233202 after replaying slot 331233202; + When voter#0 replay slot 331233204, it should find that fork 0-1-3-4 fails the lockout rule + However, when replaying slot 331233205, fork 0-1-3-4-5 should pass the lockout rule +*/ +void +test_vote_lockout_check( fd_wksp_t * wksp ) { + /**********************************************************************/ + /* Initialize funk */ + /**********************************************************************/ + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + /**********************************************************************/ + /* Initialize ghost tree */ + /**********************************************************************/ + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + + ghost_init( ghost, 331233200, funk ); + ghost_insert( ghost, 331233200, 331233201, funk ); + ghost_insert( ghost, 331233201, 331233202, funk ); + ghost_insert( ghost, 331233201, 331233203, funk ); + ghost_insert( ghost, 331233203, 331233204, funk ); + + /**********************************************************************/ + /* Initialize voters, stakes, epoch and funk_txns */ + /**********************************************************************/ + ulong voter_cnt = 5; + voter_t voters[voter_cnt]; + init_vote_accounts( voters, voter_cnt ); + + ulong stakes[] = {10000, 10000, 10000, 10000, 10000}; + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + + /**********************************************************************/ + /* Setup funk_txns for each slot with vote account funk records */ + /**********************************************************************/ + void * tower_mems[voter_cnt]; + fd_tower_t * towers[voter_cnt]; + for(ulong i = 0; i < voter_cnt; i++) { + tower_mems[i] = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + towers[i] = fd_tower_join( fd_tower_new( tower_mems[i] ) ); + } + + /**********************************************************************/ + /* Initialize landed votes per validator in funk */ + /**********************************************************************/ + voter_vote_for_slot( wksp, towers[0], funk, 331233200, 331233201, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233200, 331233201, &voters[1] ); + voter_vote_for_slot( wksp, towers[2], funk, 331233200, 331233201, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233200, 331233201, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233200, 331233201, &voters[4] ); + + voter_vote_for_slot( wksp, towers[0], funk, 331233201, 331233202, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233201, 331233202, &voters[1] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233201, 331233203, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233201, 331233203, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233201, 331233203, &voters[4] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233203, 331233204, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233203, 331233204, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233203, 331233204, &voters[4] ); + + /**********************************************************************/ + /* Initialize tower, spad and setup forks */ + /**********************************************************************/ + void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + fd_tower_t * tower = TOWER( tower_mem, 331233200, 331233201, 331233202 ); + + void * spad_mem = fd_wksp_alloc_laddr( wksp, fd_spad_align(), fd_spad_footprint( FD_TOWER_FOOTPRINT ), 5UL ); + fd_spad_t * spad = fd_spad_join( fd_spad_new( spad_mem, FD_TOWER_FOOTPRINT ) ); + + fd_forks_t * forks; + ulong frontier1 = 331233202; + ulong frontier2 = 331233204; + INIT_FORKS( frontier1 ); + ADD_FRONTIER_TO_FORKS( frontier2 ); + fd_forks_update( forks, epoch, funk, ghost, frontier1 ); + fd_forks_update( forks, epoch, funk, ghost, frontier2 ); + +/**********************************************************************/ + /* Try to vote for slot 331233204 */ + /* We should NOT switch to a different fork */ + /**********************************************************************/ + ulong try_to_vote_slot = frontier2; + fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier, &try_to_vote_slot, NULL, forks->pool ); + // Validate fd_tower_lockout_check returns 0 + FD_TEST( !fd_tower_lockout_check( tower, ghost, fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot ) ); + ulong vote_slot = fd_tower_vote_slot( tower, epoch, funk, fork->slot_ctx->funk_txn, ghost, spad ); + FD_TEST( vote_slot==ULONG_MAX ); + + /**********************************************************************/ + /* Try to vote for slot 331233205 */ + /* We should switch to a different fork */ + /**********************************************************************/ + ghost_insert( ghost, 331233204, 331233205, funk ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233204, 331233205, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233204, 331233205, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233204, 331233205, &voters[4] ); + + frontier2 = 331233205; + ADD_FRONTIER_TO_FORKS( frontier2 ); + fd_forks_update( forks, epoch, funk, ghost, frontier2 ); + + // Validate fd_tower_lockout_check returns 1 + FD_TEST( fd_tower_lockout_check( tower, ghost, fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot ) ); + vote_slot = fd_tower_vote_slot( tower, epoch, funk, fork->slot_ctx->funk_txn, ghost, spad ); + FD_TEST( vote_slot==frontier2 ); + + FD_TEST( fd_tower_votes_cnt( tower )==3 ); + fd_tower_vote( tower, vote_slot ); + FD_TEST( fd_tower_votes_cnt( tower )==3 ); + + fd_funk_close_file( &funk_close_args ); +} + +void +test_vote_threshold_check( fd_wksp_t * wksp ) { + /**********************************************************************/ + /* Initialize funk */ + /**********************************************************************/ + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + /**********************************************************************/ + /* Initialize ghost tree */ + /**********************************************************************/ + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + + ghost_init( ghost, 331233200, funk ); + ghost_insert( ghost, 331233200, 331233201, funk ); + ghost_insert( ghost, 331233201, 331233202, funk ); + ghost_insert( ghost, 331233202, 331233203, funk ); + ghost_insert( ghost, 331233203, 331233204, funk ); + ghost_insert( ghost, 331233204, 331233205, funk ); + ghost_insert( ghost, 331233205, 331233206, funk ); + ghost_insert( ghost, 331233206, 331233207, funk ); + ghost_insert( ghost, 331233207, 331233208, funk ); + ghost_insert( ghost, 331233208, 331233209, funk ); + ghost_insert( ghost, 331233209, 331233210, funk ); + + /**********************************************************************/ + /* Initialize voters, stakes, epoch and funk_txns */ + /**********************************************************************/ + ulong voter_cnt = 5; + voter_t voters[voter_cnt]; + init_vote_accounts( voters, voter_cnt ); + + ulong stakes[] = {10000, 10000, 10000, 10000, 10000}; + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + + /**********************************************************************/ + /* Initialize a funk_txn for each slot with vote account funk records */ + /**********************************************************************/ + void * tower_mems[voter_cnt]; + fd_tower_t * towers[voter_cnt]; + for(ulong i = 0; i < voter_cnt; i++) { + tower_mems[i] = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + towers[i] = fd_tower_join( fd_tower_new( tower_mems[i] ) ); + } + + /**********************************************************************/ + /* Initialize landed votes per validator in funk */ + /**********************************************************************/ + voter_vote_for_slot( wksp, towers[0], funk, 331233200, 331233201, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233200, 331233201, &voters[1] ); + voter_vote_for_slot( wksp, towers[2], funk, 331233200, 331233201, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233200, 331233201, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233200, 331233201, &voters[4] ); + + voter_vote_for_slot( wksp, towers[0], funk, 331233201, 331233202, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233201, 331233202, &voters[1] ); + voter_vote_for_slot( wksp, towers[2], funk, 331233201, 331233202, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233201, 331233202, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233201, 331233202, &voters[4] ); + + voter_vote_for_slot( wksp, towers[0], funk, 331233202, 331233203, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233202, 331233203, &voters[1] ); + voter_vote_for_slot( wksp, towers[2], funk, 331233202, 331233203, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233202, 331233203, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233202, 331233203, &voters[4] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233203, 331233204, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233203, 331233204, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233203, 331233204, &voters[4] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233204, 331233205, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233204, 331233205, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233204, 331233205, &voters[4] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233205, 331233206, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233205, 331233206, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233205, 331233206, &voters[4] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233206, 331233207, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233206, 331233207, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233206, 331233207, &voters[4] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233207, 331233208, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233207, 331233208, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233207, 331233208, &voters[4] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233208, 331233209, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233208, 331233209, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233208, 331233209, &voters[4] ); + + voter_vote_for_slot( wksp, towers[2], funk, 331233209, 331233210, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233209, 331233210, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233209, 331233210, &voters[4] ); + + /**********************************************************************/ + /* Initialize tower, spad and setup forks */ + /**********************************************************************/ + void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + fd_tower_t * tower = TOWER( tower_mem, 331233200, 331233201, 331233202, 331233203, 331233204, 331233205, 331233206, 331233207, 331233208, 331233209 ); + + void * spad_mem = fd_wksp_alloc_laddr( wksp, fd_spad_align(), fd_spad_footprint( FD_TOWER_FOOTPRINT ), 5UL ); + fd_spad_t * spad = fd_spad_join( fd_spad_new( spad_mem, FD_TOWER_FOOTPRINT ) ); + + fd_forks_t * forks; + ulong frontier1 = 331233210UL; + INIT_FORKS( frontier1 ); + + fd_forks_update( forks, epoch, funk, ghost, frontier1 ); + + /**********************************************************************/ + /* Try to vote for slot 331233210 */ + /* We should NOT switch to a different fork */ + /**********************************************************************/ + + ulong try_to_vote_slot = frontier1; + FD_TEST( try_to_vote_slot==fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot ); + fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier, &try_to_vote_slot, NULL, forks->pool ); + // Validate fd_tower_threshold_check returns 0 + FD_TEST( !fd_tower_threshold_check( tower, epoch, funk, fork->slot_ctx->funk_txn, fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot, spad ) ); + ulong vote_slot = fd_tower_vote_slot( tower, epoch, funk, fork->slot_ctx->funk_txn, ghost, spad ); + FD_TEST( vote_slot==ULONG_MAX ); + + /**********************************************************************/ + /* Suppose one more voter pass vote simulation for slot 331233210 */ + /* We should switch to a different fork */ + /**********************************************************************/ + + /* When simulating a vote for slot 331233210 with the tower above, the last 2 entries above will expire, + leaving the first 3 entries; Given that 2 >= (10-THRESHOLD_DEPTH), threshold check will pass. */ + voter_vote_for_slot( wksp, towers[0], funk, 331233203, 331233209, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233203, 331233209, &voters[1] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233204, 331233210, &voters[1] ); + + fd_forks_update( forks, epoch, funk, ghost, frontier1 ); + + // Validate fd_tower_threshold_check returns 1 + FD_TEST( fd_tower_threshold_check( tower, epoch, funk, fork->slot_ctx->funk_txn, fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot, spad ) ); + vote_slot = fd_tower_vote_slot( tower, epoch, funk, fork->slot_ctx->funk_txn, ghost, spad ); + FD_TEST( vote_slot==frontier1 ); + + fd_funk_close_file( &funk_close_args ); +} + +/* +[331233129, 331233223] + └── [331233228, 331233303] + ├── [331233304, 331233307] + └── [331233310, 331233319] + ├── [331233324] + └── [331233320, 331233323] + └── [331233326, 331233435] + └── [331233440, 331233470] + └── [331233308] +*/ +void +test_full_tower( fd_wksp_t * wksp ) { + /**********************************************************************/ + /* Initialize funk */ + /**********************************************************************/ + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 1000, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + /**********************************************************************/ + /* Initialize ghost tree */ + /**********************************************************************/ + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + + ghost_init( ghost, 331233272, funk ); + ghost_insert( ghost, 331233272, 331233273, funk ); + ghost_insert( ghost, 331233273, 331233274, funk ); + ghost_insert( ghost, 331233274, 331233275, funk ); + ghost_insert( ghost, 331233275, 331233276, funk ); + ghost_insert( ghost, 331233276, 331233277, funk ); + ghost_insert( ghost, 331233277, 331233278, funk ); + ghost_insert( ghost, 331233278, 331233279, funk ); + ghost_insert( ghost, 331233279, 331233280, funk ); + ghost_insert( ghost, 331233280, 331233281, funk ); + ghost_insert( ghost, 331233281, 331233282, funk ); + ghost_insert( ghost, 331233282, 331233283, funk ); + ghost_insert( ghost, 331233283, 331233284, funk ); + ghost_insert( ghost, 331233284, 331233285, funk ); + ghost_insert( ghost, 331233285, 331233286, funk ); + ghost_insert( ghost, 331233286, 331233287, funk ); + ghost_insert( ghost, 331233287, 331233288, funk ); + ghost_insert( ghost, 331233288, 331233289, funk ); + ghost_insert( ghost, 331233289, 331233290, funk ); + ghost_insert( ghost, 331233290, 331233291, funk ); + ghost_insert( ghost, 331233291, 331233292, funk ); + ghost_insert( ghost, 331233292, 331233293, funk ); + ghost_insert( ghost, 331233293, 331233294, funk ); + ghost_insert( ghost, 331233294, 331233295, funk ); + ghost_insert( ghost, 331233295, 331233296, funk ); + ghost_insert( ghost, 331233296, 331233297, funk ); + ghost_insert( ghost, 331233297, 331233298, funk ); + ghost_insert( ghost, 331233298, 331233299, funk ); + ghost_insert( ghost, 331233299, 331233300, funk ); + ghost_insert( ghost, 331233300, 331233301, funk ); + ghost_insert( ghost, 331233301, 331233302, funk ); + ghost_insert( ghost, 331233302, 331233303, funk ); + ghost_insert( ghost, 331233303, 331233304, funk ); + ghost_insert( ghost, 331233304, 331233305, funk ); + ghost_insert( ghost, 331233305, 331233306, funk ); + ghost_insert( ghost, 331233305, 331233307, funk ); + ghost_insert( ghost, 331233303, 331233308, funk ); + ghost_insert( ghost, 331233308, 331233309, funk ); + ghost_insert( ghost, 331233307, 331233310, funk ); + ghost_insert( ghost, 331233310, 331233311, funk ); + ghost_insert( ghost, 331233311, 331233312, funk ); + ghost_insert( ghost, 331233312, 331233313, funk ); + ghost_insert( ghost, 331233313, 331233314, funk ); + ghost_insert( ghost, 331233314, 331233315, funk ); + ghost_insert( ghost, 331233315, 331233316, funk ); + ghost_insert( ghost, 331233316, 331233317, funk ); + ghost_insert( ghost, 331233317, 331233318, funk ); + ghost_insert( ghost, 331233318, 331233319, funk ); + ghost_insert( ghost, 331233319, 331233320, funk ); + ghost_insert( ghost, 331233320, 331233321, funk ); + ghost_insert( ghost, 331233321, 331233322, funk ); + ghost_insert( ghost, 331233322, 331233323, funk ); + ghost_insert( ghost, 331233319, 331233324, funk ); + ghost_insert( ghost, 331233324, 331233325, funk ); + ghost_insert( ghost, 331233323, 331233326, funk ); + ghost_insert( ghost, 331233326, 331233327, funk ); + + /**********************************************************************/ + /* Initialize voters, stakes, epoch and funk_txns */ + /**********************************************************************/ + ulong voter_cnt = 5; + voter_t voters[voter_cnt]; + init_vote_accounts( voters, voter_cnt ); + + ulong stakes[] = {12, 27, 16, 7, 38}; + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + + /**********************************************************************/ + /* Setup funk_txns for each slot with vote account funk records */ + /**********************************************************************/ + void * tower_mems[voter_cnt]; + fd_tower_t * towers[voter_cnt]; + for(ulong i = 0; i < voter_cnt; i++) { + tower_mems[i] = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + towers[i] = TOWER( tower_mems[i], 331233272, 331233273, 331233274, + 331233275, 331233276, 331233277, 331233278, + 331233279, 331233280, 331233281, 331233282, + 331233283, 331233284, 331233285, 331233286, + 331233287, 331233288, 331233289, 331233290, + 331233291, 331233292, 331233293, 331233294, + 331233295, 331233296, 331233297, 331233298, + 331233299, 331233300, 331233301, 331233302); + } + + /**********************************************************************/ + /* Initialize landed votes per validator in funk */ + /**********************************************************************/ + + voter_vote_for_slot( wksp, towers[0], funk, 331233303, 331233304, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 331233303, 331233304, &voters[1] ); + voter_vote_for_slot( wksp, towers[2], funk, 331233303, 331233304, &voters[2] ); + voter_vote_for_slot( wksp, towers[3], funk, 331233303, 331233304, &voters[3] ); + voter_vote_for_slot( wksp, towers[4], funk, 331233303, 331233304, &voters[4] ); + + /**********************************************************************/ + /* Initialize tower, spad and setup forks */ + /**********************************************************************/ + void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + fd_tower_t * tower = TOWER( tower_mem, 331233272, 331233273, 331233274, + 331233275, 331233276, 331233277, 331233278, + 331233279, 331233280, 331233281, 331233282, + 331233283, 331233284, 331233285, 331233286, + 331233287, 331233288, 331233289, 331233290, + 331233291, 331233292, 331233293, 331233294, + 331233295, 331233296, 331233297, 331233298, + 331233299, 331233300, 331233301, 331233302 ); + FD_TEST( tower ); + + void * spad_mem = fd_wksp_alloc_laddr( wksp, fd_spad_align(), fd_spad_footprint( FD_TOWER_FOOTPRINT ), 5UL ); + fd_spad_t * spad = fd_spad_join( fd_spad_new( spad_mem, FD_TOWER_FOOTPRINT ) ); + + fd_forks_t * forks; + ulong frontier1 = 331233304UL; + ulong frontier2 = 331233308UL; + INIT_FORKS( frontier1 ); + ADD_FRONTIER_TO_FORKS( frontier2 ); + + fd_forks_update( forks, epoch, funk, ghost, frontier1 ); + fd_ghost_print( ghost, epoch, fd_ghost_root(ghost) ); + + voter_vote_for_slot( wksp, towers[1], funk, 331233304, 331233304, &voters[1] ); + + fd_forks_update( forks, epoch, funk, ghost, frontier1 ); + fd_ghost_print( ghost, epoch, fd_ghost_root(ghost) ); + + /* Everyone has voted for slot 331233302 in their towers by slot 331233303 */ + // fd_tower_t * tower = mock_tower( wksp, 31, votes ); + + // ulong root = fd_tower_vote( tower, 331233303 ); + // fd_tower_vote( tower, 331233303 ); + // root = fd_tower_vote( tower, 331233304 ); + // fd_tower_print( tower, root ); + + // fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier, &frontier, NULL, forks->pool ); + // FD_TEST( fork ); + // VOTE_FOR_SLOT( 331233303UL, 331233304, 0 ); + FD_TEST( spad ); + fd_funk_close_file( &funk_close_args ); +} + +void +test_agave_max_by_weight( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + ghost_init( ghost, 0, funk ); + ghost_insert( ghost, 0, 4, funk ); + ghost_insert( ghost, 4, 5, funk ); + + ulong voter_cnt = 1; + voter_t voters[voter_cnt]; + ulong stakes[] = {100}; + init_vote_accounts( voters, voter_cnt ); + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + + fd_ghost_replay_vote( ghost, voter0, 4 ); + FD_TEST( fd_ghost_query( ghost, 4 )->weight>fd_ghost_query( ghost, 5 )->weight ); + + fd_ghost_node_t const * node4 = fd_ghost_query( ghost, 4 ); + fd_ghost_node_t const * node0 = fd_ghost_query( ghost, 0 ); + /* The following pattern is used in function fd_ghost_head */ + FD_TEST( node0==fd_ptr_if( fd_int_if( node4->weight==node0->weight, + node4->slotslot, node4->weight>node0->weight ), + node4, node0 ) ); + + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test max_by_weight" )); +} + +void +test_agave_add_root_parent( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + ghost_init( ghost, 3, funk ); + ghost_insert( ghost, 3, 4, funk ); + ghost_insert( ghost, 4, 5, funk ); + + ulong voter_cnt = 1; + voter_t voters[voter_cnt]; + ulong stakes[] = {100}; + init_vote_accounts( voters, voter_cnt ); + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + fd_ghost_replay_vote( ghost, voter0, 5 ); + + /**********************************************************************/ + /* Add slot #2 as the new root */ + /**********************************************************************/ + ulong old_root=3; + ulong new_root=2; + fd_ghost_node_t * node_pool = fd_ghost_node_pool( ghost ); + fd_ghost_node_map_t * node_map = fd_ghost_node_map( ghost ); + ulong null_idx = fd_ghost_node_pool_idx_null( node_pool ); + + /* Initialize and insert the root node from a pool element. */ + fd_ghost_node_t * slot_2_node = fd_ghost_node_pool_ele_acquire( node_pool ); + memset( slot_2_node, 0, sizeof( fd_ghost_node_t ) ); + slot_2_node->slot = new_root; + slot_2_node->next = null_idx; + slot_2_node->valid = 1; + slot_2_node->parent_idx = null_idx; + slot_2_node->child_idx = fd_ghost_node_map_idx_query( node_map, &old_root, null_idx, node_pool ); + slot_2_node->sibling_idx = null_idx; + fd_ghost_node_map_ele_insert( node_map, slot_2_node, node_pool ); /* cannot fail */ + + /* Update root and old root node */ + fd_ghost_node_t * slot_3_node = fd_ghost_node_map_ele_query( node_map, &old_root, NULL, node_pool ); + ulong new_root_idx=fd_ghost_node_map_idx_query( node_map, &new_root, null_idx, node_pool ); + slot_3_node->parent_idx = ghost->root_idx = new_root_idx; + + /**********************************************************************/ + /* Check the updated ghost tree */ + /**********************************************************************/ + FD_TEST( fd_ghost_node_pool_ele( node_pool, slot_3_node->parent_idx )->slot==2 ); + FD_TEST( fd_ghost_query( ghost, 3 )->weight==100 ); + FD_TEST( fd_ghost_query( ghost, 2 )->weight==0 ); + FD_TEST( slot_2_node->child_idx==fd_ghost_node_map_idx_query( node_map, &old_root /* 3 */, null_idx, node_pool ) + && slot_3_node->sibling_idx==null_idx ); + FD_TEST( fd_ghost_head( ghost, slot_2_node )->slot==5 ); + FD_LOG_WARNING(( "Agave then tests whether slot 5 is the *deepest* slot from slot 2, " + "but it seems sthat FD does not have this functionality." )); + FD_TEST( slot_2_node->parent_idx==null_idx ); + + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test add_root_parent" )); +} + +fd_ghost_t * +test_agave_setup_forks( fd_wksp_t * wksp, + fd_funk_t * funk ) { + /* + Build fork structure: + slot 0 + | + slot 1 + / \ + slot 2 | + | slot 3 + slot 4 | + slot 5 + | + slot 6 + */ + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + ghost_init( ghost, 0, funk ); + ghost_insert( ghost, 0, 1, funk ); + ghost_insert( ghost, 1, 2, funk ); + ghost_insert( ghost, 2, 4, funk ); + ghost_insert( ghost, 1, 3, funk ); + ghost_insert( ghost, 3, 5, funk ); + ghost_insert( ghost, 5, 6, funk ); + + return ghost; +} + +void +test_agave_ancestor_iterator( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + fd_ghost_t * ghost = test_agave_setup_forks( wksp, funk ); + + const fd_ghost_node_t * test1_node6 = fd_ghost_query( ghost, 6 ); + const fd_ghost_node_t * test1_node5 = fd_ghost_parent( ghost, test1_node6 ); + FD_TEST( test1_node5->slot==5 ); + const fd_ghost_node_t * test1_node3 = fd_ghost_parent( ghost, test1_node5 ); + FD_TEST( test1_node3->slot==3 ); + const fd_ghost_node_t * test1_node1 = fd_ghost_parent( ghost, test1_node3 ); + FD_TEST( test1_node1->slot==1 ); + const fd_ghost_node_t * test1_node0 = fd_ghost_parent( ghost, test1_node1 ); + FD_TEST( test1_node0->slot==0 ); + FD_TEST( NULL==fd_ghost_parent( ghost, test1_node0 ) ); + + const fd_ghost_node_t * test2_node4 = fd_ghost_query( ghost, 4 ); + const fd_ghost_node_t * test2_node2 = fd_ghost_parent( ghost, test2_node4 ); + FD_TEST( test2_node2->slot==2 ); + const fd_ghost_node_t * test2_node1 = fd_ghost_parent( ghost, test2_node2 ); + FD_TEST( test2_node1->slot==1 ); + const fd_ghost_node_t * test2_node0 = fd_ghost_parent( ghost, test2_node1 ); + FD_TEST( test2_node0->slot==0 ); + FD_TEST( NULL==fd_ghost_parent( ghost, test2_node0 ) ); + + const fd_ghost_node_t * test3_node1 = fd_ghost_query( ghost, 1 ); + const fd_ghost_node_t * test3_node0 = fd_ghost_parent( ghost, test3_node1 ); + FD_TEST( test3_node0->slot==0 ); + FD_TEST( NULL==fd_ghost_parent( ghost, test3_node0 ) ); + + const fd_ghost_node_t * test4_node0 = fd_ghost_query( ghost, 0 ); + FD_TEST( NULL==fd_ghost_parent( ghost, test4_node0 ) ); + + // Set a root, everything but slots 2, 4 should be removed + fd_ghost_publish( ghost, 2 ); + const fd_ghost_node_t * test5_node4 = fd_ghost_query( ghost, 4 ); + const fd_ghost_node_t * test5_node2 = fd_ghost_parent( ghost, test5_node4 ); + FD_TEST( test5_node2->slot==2 ); + FD_TEST( NULL==fd_ghost_parent( ghost, test5_node2 ) ); + + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test ancestor_iterator" )); +} + +void +test_agave_new_from_frozen_banks( fd_wksp_t * wksp FD_PARAM_UNUSED ) { + FD_LOG_WARNING(( "In Agave, HeaviestSubtreeForkChoice maintains (slot#, bank_hash), " + "but FD only maintains slot# in fd_ghost_node_t. In this unit test, " + "Agave mostly tries to test the bank_hash part. This may have some " + "implications to how Agave handles duplication (different bank_hash " + "with the same slot#). Need to revisit it, probably in a later test." )); +} + +void +test_agave_set_root( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + fd_ghost_t * ghost = test_agave_setup_forks( wksp, funk ); + + // Set root to 1, should only purge 0 + fd_ghost_publish( ghost, 1 ); + for( ulong slot=0; slot<=6; slot++ ){ + if( slot==0 ) { + FD_TEST( NULL==fd_ghost_query( ghost, slot ) ); + } else { + FD_TEST( NULL!=fd_ghost_query( ghost, slot ) ); + } + } + + // Set root to 5, should purge everything except 5, 6 + fd_ghost_publish( ghost, 5 ); + for( ulong slot=0; slot<=6; slot++ ) { + if( slot!=5 && slot!=6 ) { + FD_TEST( NULL==fd_ghost_query( ghost, slot ) ); + } else { + FD_TEST( NULL!=fd_ghost_query( ghost, slot ) ); + } + } + + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test set_root" )); +} + +void +test_agave_set_root_and_add_votes( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + fd_ghost_t * ghost = test_agave_setup_forks( wksp, funk ); + ulong voter_cnt = 1; + voter_t voters[voter_cnt]; + ulong stakes[] = {100}; + init_vote_accounts( voters, voter_cnt ); + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + + // Vote for slot 2 + fd_ghost_replay_vote( ghost, voter0, 2 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==4 ); + + // Set a root + fd_ghost_publish( ghost, 1 ); + + // Vote again for slot 3 on a different fork than the last vote, + // verify this fork is now the best fork + fd_ghost_replay_vote( ghost, voter0, 3 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==6 ); + FD_TEST( fd_ghost_query( ghost, 1 )->replay_stake==0 ); + FD_TEST( fd_ghost_query( ghost, 3 )->replay_stake==100 ); + FD_TEST( fd_ghost_query( ghost, 1 )->weight==100 ); + FD_TEST( fd_ghost_query( ghost, 3 )->weight==100 ); + + // Set a root at last vote + fd_ghost_publish( ghost, 3 ); + // Check new leaf 7 is still propagated properly + ghost_insert( ghost, 6, 7, funk ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==7 ); + + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test set_root_and_add_votes" )); +} + +void +test_agave_set_root_and_add_outdated_votes( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + fd_ghost_t * ghost = test_agave_setup_forks( wksp, funk ); + ulong voter_cnt = 1; + voter_t voters[voter_cnt]; + ulong stakes[] = {100}; + init_vote_accounts( voters, voter_cnt ); + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + + // Vote for slot 0 + fd_ghost_replay_vote( ghost, voter0, 0 ); + + // Set root to 1, should purge 0 from the tree, but + // there's still an outstanding vote for slot 0 in `pubkey_votes`. + fd_ghost_publish( ghost, 1 ); + + // Vote again for slot 3, verify everything is ok + fd_ghost_replay_vote( ghost, voter0, 3 ); + FD_TEST( fd_ghost_query( ghost, 3 )->replay_stake==100 ); + FD_TEST( fd_ghost_query( ghost, 1 )->weight==100 ); + FD_TEST( fd_ghost_query( ghost, 3 )->weight==100 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==6 ); + + // Set root again on different fork than the last vote + fd_ghost_publish( ghost, 2 ); + // Smaller vote than last vote 3 should be ignored + fd_ghost_replay_vote( ghost, voter0, 2 ); + FD_TEST( fd_ghost_query( ghost, 2 )->replay_stake==0 ); + FD_TEST( fd_ghost_query( ghost, 2 )->weight==0 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==4 ); + + // New larger vote than last vote 3 should be processed + fd_ghost_replay_vote( ghost, voter0, 4 ); + FD_TEST( fd_ghost_query( ghost, 2 )->replay_stake==0 ); + FD_TEST( fd_ghost_query( ghost, 4 )->replay_stake==100 ); + FD_TEST( fd_ghost_query( ghost, 2 )->weight==100 ); + FD_TEST( fd_ghost_query( ghost, 4 )->weight==100 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==4 ); + + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test set_root_and_add_outdated_votes" )); +} + +void +test_agave_best_overall_slot( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + fd_ghost_t * ghost = test_agave_setup_forks( wksp, funk ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==4 ); + + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test best_overall_slot" )); +} + +void +test_agave_propagate_new_leaf( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + fd_ghost_t * ghost = test_agave_setup_forks( wksp, funk ); + + // Add a leaf 10, it should be the best and deepest choice + ghost_insert( ghost, 4, 10, funk ); + for( fd_ghost_node_t * node=(fd_ghost_node_t *)fd_ghost_query( ghost, 10 ); + node!=NULL; + node=(fd_ghost_node_t *)fd_ghost_parent( ghost, node ) ) { + FD_TEST( fd_ghost_head( ghost, node )->slot==10 ); + // TODO: check deepest slot as well + } + + // Add a smaller leaf 9, it should be the best and deepest choice + ghost_insert( ghost, 4, 9, funk ); + for( fd_ghost_node_t * node=(fd_ghost_node_t *)fd_ghost_query( ghost, 9 ); + node!=NULL; + node=(fd_ghost_node_t *)fd_ghost_parent( ghost, node ) ) { + FD_TEST( fd_ghost_head( ghost, node )->slot==9 ); + // TODO: check deepest slot as well + } + + // Add a higher leaf 11, should not change the best or deepest choice + ghost_insert( ghost, 4, 11, funk ); + for( fd_ghost_node_t * node=(fd_ghost_node_t *)fd_ghost_query( ghost, 9 ); + node!=NULL; + node=(fd_ghost_node_t *)fd_ghost_parent( ghost, node ) ) { + FD_TEST( fd_ghost_head( ghost, node )->slot==9 ); + // TODO: check deepest slot as well + } + + ulong voter_cnt = 2; + voter_t voters[voter_cnt]; + ulong stakes[] = {100}; + init_vote_accounts( voters, voter_cnt ); + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + fd_voter_t * voter1 = fd_epoch_voters( epoch )+1; + + // Leaf slot 9 stops being the `best_slot` at slot 1 because there + // are now votes for the branch at slot 3 + fd_ghost_replay_vote( ghost, voter0, 6 ); + + // Because slot 1 now sees the child branch at slot 3 has non-zero + // weight, adding smaller leaf slot 8 in the other child branch at slot 2 + // should not propagate past slot 1 + // Similarly, both forks have the same tree height so we should tie break by + // stake weight choosing 6 as the deepest slot when possible. + ghost_insert( ghost, 4, 8, funk ); + for( fd_ghost_node_t * node=(fd_ghost_node_t *)fd_ghost_query( ghost, 8 ); + node!=NULL; + node=(fd_ghost_node_t *)fd_ghost_parent( ghost, node ) ) { + ulong best_slot=( node->slot>1 ) ? 8 : 6; + FD_TEST( fd_ghost_head( ghost, node )->slot==best_slot ); + // TODO: check deepest slot as well + } + + // Add vote for slot 8, should now be the best slot (has same weight + // as fork containing slot 6, but slot 2 is smaller than slot 3). + fd_ghost_replay_vote( ghost, voter1, 8 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==8 ); + // TODO: check deepest slot as well + + // Because slot 4 now sees the child leaf 8 has non-zero + // weight, adding smaller leaf slots should not propagate past slot 4 + // Similarly by tiebreak, 8 should be the deepest slot + ghost_insert( ghost, 4, 7, funk ); + for( fd_ghost_node_t * node=(fd_ghost_node_t *)fd_ghost_query( ghost, 8 ); + node!=NULL; + node=(fd_ghost_node_t *)fd_ghost_parent( ghost, node ) ) { + FD_TEST( fd_ghost_head( ghost, node )->slot==8 ); + // TODO: check deepest slot as well + } + + FD_TEST( fd_ghost_head( ghost, fd_ghost_query( ghost, 8 ) )->slot==8 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_query( ghost, 9 ) )->slot==9 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_query( ghost, 10 ) )->slot==10 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_query( ghost, 11 ) )->slot==11 ); + // TODO: check deepest slot as well + + fd_funk_close_file( &funk_close_args ); + FD_LOG_WARNING(( "Pass Agave unit test propagate_new_leaf w/o several tests for the deepest slot" )); +} + +void +test_agave_propagate_new_leaf_2( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + ghost_init( ghost, 0, funk ); + ghost_insert( ghost, 0, 4, funk ); + ghost_insert( ghost, 4, 6, funk ); + + ulong voter_cnt = 1; + voter_t voters[voter_cnt]; + ulong stakes[] = {100}; + init_vote_accounts( voters, voter_cnt ); + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + + // slot 6 should be the best because it's the only leaf + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==6 ); + + // Add a leaf slot 5. Even though 5 is less than the best leaf 6, + // it's not less than it's sibling slot 4, so the best overall + // leaf should remain unchanged + ghost_insert( ghost, 0, 5, funk ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==6 ); + + // Add a leaf slot 2 on a different fork than leaf 6. Slot 2 should + // be the new best because it's for a lesser slot + ghost_insert( ghost, 0, 2, funk ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==2 ); + + // Add a vote for slot 4, so leaf 6 should be the best again + fd_ghost_replay_vote( ghost, voter0, 4 ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==6 ); + + // Adding a slot 1 that is less than the current best leaf 6 should not change the best + // slot because the fork slot 5 is on has a higher weight + ghost_insert( ghost, 0, 1, funk ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==6 ); + + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test propagate_new_leaf_2" )); +} + +void +test_agave_aggregate_slot( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + fd_ghost_t * ghost = test_agave_setup_forks( wksp, funk ); + (void)ghost; + FD_LOG_WARNING(( "This function tests HeaviestSubtreeForkChoice.aggregate_slot() which " + "is close to fd_ghost_replay_vote() and fd_ghost_head(), but we don't have " + "certain fields in ghost node: height, deepest_slot, is_duplicate_confimed." )); + + fd_funk_close_file( &funk_close_args ); +} + +void +test_agave_process_update_operations( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + fd_ghost_t * ghost = test_agave_setup_forks( wksp, funk ); + ulong voter_cnt = 3; + voter_t voters[voter_cnt]; + ulong stakes[] = {100, 100, 100}; + init_vote_accounts( voters, voter_cnt ); + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + + /* Note: our code processes ghost tree updates with fd_ghost_replay_vote() + * which seems a lot simpler than how Agave generates update operations. */ + { + // Voter 0, 1, 2 vote for slot 3, 2, 1 respectively + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + fd_ghost_replay_vote( ghost, voter0, 3 ); + fd_voter_t * voter1 = fd_epoch_voters( epoch )+1; + fd_ghost_replay_vote( ghost, voter1, 2 ); + fd_voter_t * voter2 = fd_epoch_voters( epoch )+2; + fd_ghost_replay_vote( ghost, voter2, 1 ); + /* 0 1 2 3 4 5 6 */ + ulong expected_weight[] = { 3*100, 3*100, 1*100, 1*100, 0, 0, 0 }; + ulong expected_replay_stake[] = { 0, 100, 100, 100, 0, 0, 0 }; + ulong expected_best_slot[] = { 4, 4, 4, 6, 4, 6, 6 }; + for( ulong slot=0; slot<=6; slot++ ) { + FD_TEST( fd_ghost_query( ghost, slot )->weight==expected_weight[slot] ); + FD_TEST( fd_ghost_query( ghost, slot )->replay_stake==expected_replay_stake[slot] ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_query( ghost, slot ) )->slot==expected_best_slot[slot] ); + /* TODO: deepest slot */ + } + } + + { + // Voter 0, 1, 2 now vote for slot 4, 3, 3 instead + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + fd_ghost_replay_vote( ghost, voter0, 4 ); + fd_voter_t * voter1 = fd_epoch_voters( epoch )+1; + fd_ghost_replay_vote( ghost, voter1, 3 ); + fd_voter_t * voter2 = fd_epoch_voters( epoch )+2; + fd_ghost_replay_vote( ghost, voter2, 3 ); + /* 0 1 2 3 4 5 6 */ + ulong expected_weight[] = { 3*100, 3*100, 1*100, 2*100, 1*100, 0, 0 }; + ulong expected_replay_stake[] = { 0, 0, 0, 200, 100, 0, 0 }; + ulong expected_best_slot[] = { 6, 6, 4, 6, 4, 6, 6 }; + for( ulong slot=0; slot<=6; slot++ ) { + FD_TEST( fd_ghost_query( ghost, slot )->weight==expected_weight[slot] ); + FD_TEST( fd_ghost_query( ghost, slot )->replay_stake==expected_replay_stake[slot] ); + FD_TEST( fd_ghost_head( ghost, fd_ghost_query( ghost, slot ) )->slot==expected_best_slot[slot] ); + /* TODO: deepest slot */ + } + } + + fd_funk_close_file( &funk_close_args ); + FD_LOG_WARNING(( "Pass Agave unit test process_update_operations w/o checking deepest slot" )); +} + +void +test_agave_generate_update_operations( fd_wksp_t * wksp FD_PARAM_UNUSED ) { + FD_LOG_NOTICE(( "Skip Agave unit test genereate_update_operations because " + "we update ghost tree differently with fd_ghost_replay_vote()" )); +} + +void +test_agave_add_votes( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + fd_ghost_t * ghost = test_agave_setup_forks( wksp, funk ); + ulong voter_cnt = 3; + voter_t voters[voter_cnt]; + ulong stakes[] = {100, 100, 100}; + init_vote_accounts( voters, voter_cnt ); + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + + /* Note: our code processes ghost tree updates with fd_ghost_replay_vote() + * which seems a lot simpler than how Agave generates update operations. */ + { + // Voter 0, 1, 2 vote for slot 3, 2, 1 respectively + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + fd_ghost_replay_vote( ghost, voter0, 3 ); + fd_voter_t * voter1 = fd_epoch_voters( epoch )+1; + fd_ghost_replay_vote( ghost, voter1, 2 ); + fd_voter_t * voter2 = fd_epoch_voters( epoch )+2; + fd_ghost_replay_vote( ghost, voter2, 1 ); + + FD_TEST( fd_ghost_head( ghost, fd_ghost_root( ghost ) )->slot==4 ); + } + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test add_votes" )); +} + +static int +fd_is_best_child( fd_ghost_t const * ghost, fd_ghost_node_t const * node ) { + /* the logic is copy-pasted from fd_ghost_head in fd_ghost.c */ + fd_ghost_node_t const * node_pool = fd_ghost_node_pool_const( ghost ); + ulong null_idx = fd_ghost_node_pool_idx_null( node_pool ); + if( node->parent_idx==null_idx ) return 1; + + fd_ghost_node_t const * parent = fd_ghost_node_pool_ele_const( node_pool, node->parent_idx ); + fd_ghost_node_t const * head = fd_ghost_node_pool_ele_const( node_pool, parent->child_idx ); + fd_ghost_node_t const * curr = head; + while( curr ) { + head = fd_ptr_if( + fd_int_if( + /* if the weights are equal... */ + + curr->weight == head->weight, + + /* ...tie-break by slot number */ + + curr->slot < head->slot, + + /* otherwise return curr if curr > head */ + + curr->weight > head->weight ), + curr, head ); + + curr = fd_ghost_node_pool_ele_const( node_pool, curr->sibling_idx ); + } + return head==node; +} + +void +test_agave_is_best_child( fd_wksp_t * wksp ) { + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + ghost_init( ghost, 0, funk ); + ghost_insert( ghost, 0, 4, funk ); + ghost_insert( ghost, 4, 9, funk ); + ghost_insert( ghost, 4, 10, funk ); + + /* We don't have is_best_child, but the logic is part of fd_ghost_head and + * the unit test here splits that logic into a static function. */ + FD_TEST( fd_is_best_child( ghost, fd_ghost_query( ghost, 0 ) ) ); + FD_TEST( fd_is_best_child( ghost, fd_ghost_query( ghost, 4 ) ) ); + + // 9 is better than 10 + FD_TEST( fd_is_best_child( ghost, fd_ghost_query( ghost, 9 ) ) ); + FD_TEST( !fd_is_best_child( ghost, fd_ghost_query( ghost, 10 ) ) ); + + // Add new leaf 8, which is better than 9, as both have weight 0 + ghost_insert( ghost, 4, 8, funk ); + FD_TEST( fd_is_best_child( ghost, fd_ghost_query( ghost, 8 ) ) ); + FD_TEST( !fd_is_best_child( ghost, fd_ghost_query( ghost, 9 ) ) ); + FD_TEST( !fd_is_best_child( ghost, fd_ghost_query( ghost, 10 ) ) ); + + // Add vote for 9, it's the best again + ulong voter_cnt = 3; + voter_t voters[voter_cnt]; + ulong stakes[] = {100, 100, 100}; + init_vote_accounts( voters, voter_cnt ); + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + fd_voter_t * voter0 = fd_epoch_voters( epoch ); + fd_ghost_replay_vote( ghost, voter0, 9 ); + FD_TEST( fd_is_best_child( ghost, fd_ghost_query( ghost, 9 ) ) ); + FD_TEST( !fd_is_best_child( ghost, fd_ghost_query( ghost, 8 ) ) ); + FD_TEST( !fd_is_best_child( ghost, fd_ghost_query( ghost, 10 ) ) ); + + fd_funk_close_file( &funk_close_args ); + FD_LOG_NOTICE(( "Pass Agave unit test is_best_child" )); +} + +void +test_agave_collect_vote_lockouts_sums( fd_wksp_t * wksp ) { + /**********************************************************************/ + /* Initialize funk */ + /**********************************************************************/ + fd_funk_close_file_args_t funk_close_args; + fd_funk_t * funk = fd_funk_open_file( "", 1, 0, 1000, 100, 1*(1UL<<30), FD_FUNK_OVERWRITE, &funk_close_args ); + FD_TEST( funk ); + + /**********************************************************************/ + /* Initialize ghost tree */ + /**********************************************************************/ + void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( FD_BLOCK_MAX ), 1UL ); + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 0UL, FD_BLOCK_MAX ) ); + + ghost_init( ghost, 0, funk ); + ghost_insert( ghost, 0, 1, funk ); + + /**********************************************************************/ + /* Initialize voters, stakes, epoch and funk_txns */ + /**********************************************************************/ + ulong voter_cnt = 2; + voter_t voters[voter_cnt]; + init_vote_accounts( voters, voter_cnt ); + + ulong stakes[] = {1, 1}; + fd_epoch_t * epoch = mock_epoch( wksp, voter_cnt, stakes, voters ); + + /**********************************************************************/ + /* Setup funk_txns for each slot with vote account funk records */ + /**********************************************************************/ + void * tower_mems[voter_cnt]; + fd_tower_t * towers[voter_cnt]; + for(ulong i = 0; i < voter_cnt; i++) { + tower_mems[i] = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint(), 6UL ); + towers[i] = fd_tower_join( fd_tower_new( tower_mems[i] ) ); + } + + /**********************************************************************/ + /* Initialize landed votes per validator in funk */ + /**********************************************************************/ + voter_vote_for_slot( wksp, towers[0], funk, 0, 1, &voters[0] ); + voter_vote_for_slot( wksp, towers[1], funk, 0, 1, &voters[1] ); + + /**********************************************************************/ + /* Initialize tower, spad and setup forks */ + /**********************************************************************/ + fd_forks_t * forks; + ulong curr_slot = 1; + INIT_FORKS( curr_slot ); + fd_forks_update( forks, epoch, funk, ghost, curr_slot ); + + FD_TEST( fd_ghost_query( ghost, 0 )->replay_stake==2 ); + FD_TEST( fd_ghost_query( ghost, fd_ghost_root( ghost )->slot )->replay_stake==2 ); + + // Check that all voters have voted for slot#0 in the funk txn for slot#1 + fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier, &curr_slot, NULL, forks->pool ); + fd_funk_txn_t * txn = fork->slot_ctx->funk_txn; + fd_voter_t * epoch_voters = fd_epoch_voters( epoch ); + for( ulong i=0; irec ); + vote = fd_voter_state_vote( state ); + if( FD_LIKELY( fd_funk_rec_query_test( query ) == FD_FUNK_SUCCESS ) ) { + break; + } + } + FD_TEST( vote==0 ); + //TODO: check the hash being voted in addition to the slot# + } + + FD_LOG_NOTICE(( "Pass Agave test collect_vote_lockouts_sum" )); + fd_funk_close_file( &funk_close_args ); +} + +int +main( int argc, char ** argv ) { + fd_boot( &argc, &argv ); + + ulong page_cnt = 10; + char * _page_sz = "gigantic"; + ulong numa_idx = fd_shmem_numa_idx( 0 ); + fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), + page_cnt, + fd_shmem_cpu_idx( numa_idx ), + "wksp", + 0UL ); + FD_TEST( wksp ); + + test_vote_simple( wksp ); + test_vote_switch_check( wksp ); + test_vote_lockout_check( wksp ); + test_vote_threshold_check( wksp ); + test_vote_switch_check_4forks( wksp ); + test_full_tower( wksp ); + + /* Below are unit tests from Agave core/src/consensus/heaviest_subtree_fork_choice.rs */ + test_agave_max_by_weight( wksp ); + test_agave_add_root_parent( wksp ); + test_agave_ancestor_iterator( wksp ); + test_agave_new_from_frozen_banks( wksp ); /* Not passing */ + test_agave_set_root( wksp ); + test_agave_set_root_and_add_votes( wksp ); + test_agave_set_root_and_add_outdated_votes( wksp ); + test_agave_best_overall_slot( wksp ); + test_agave_propagate_new_leaf( wksp ); /* Need deepest slot */ + test_agave_propagate_new_leaf_2( wksp ); + test_agave_aggregate_slot( wksp ); /* Not passing */ + test_agave_process_update_operations( wksp ); /* Need deepest slot */ + test_agave_generate_update_operations( wksp ); + test_agave_add_votes( wksp ); + test_agave_is_best_child( wksp ); + + FD_LOG_WARNING(( "The 1 test below requires `stray_restored_slot` in fd_tower_t " + "for handling an edge case, but it seems missing on our side." )); + /* test_stray_restored_slot() */ + + FD_LOG_WARNING(( "The 5 tests below use fd_ghost_node->valid, but it seems not maintained." )); + /* test_mark_valid_invalid_forks() + * test_mark_valid_then_descendant_invalid() + * test_mark_valid_then_ancestor_invalid() + * test_set_unconfirmed_duplicate_confirm_smaller_slot_first() + * test_set_unconfirmed_duplicate_confirm_larger_slot_first() */ + + /* slot 0 + | + slot 1 + / \ + slot 2 | + | slot 3 + slot 4 \ + / \ slot 5 + slot 10 slot 10 / | \ + slot 6 slot 10 slot 10 + / \ + slot 10 slot 10 + All the "slot 10" above have different bank hashes. */ + FD_LOG_WARNING(( "The 10 tests below use the tree above with duplicate " + "slot 10s which seems not supported by fd_ghost_node_t." )); + /* test_add_new_leaf_duplicate() + * test_add_votes_duplicate_tie() + * test_add_votes_duplicate_greater_hash_ignored() + * test_add_votes_duplicate_smaller_hash_prioritized() + * test_add_votes_duplicate_then_outdated() + * test_add_votes_duplicate_zero_stake() + * test_mark_invalid_then_valid_duplicate() + * test_mark_invalid_then_add_new_heavier_duplicate_slot() */ + + FD_LOG_NOTICE(( "Skip 8 Agave unit tests test_split_off_*() " + "because tree split seems only used in repair instead of consensus." )); + FD_LOG_NOTICE(( "Skip 2 Agave unit tests test_merge() and test_merge_duplicate() " + "because tree merge seems only used in repair instead of consensus." )); + FD_LOG_NOTICE(( "Skip 2 Agave unit tests test_purge_prune() and test_purge_prune_complicated() " + "because purge_prune seems only used in repair instead of consensus." )); + FD_LOG_NOTICE(( "Skip 1 Agave unit test test_subtree_diff() " + "because it is only used in set_tree_root which has already been tested." )); + + /* Below are unit tests from Agave core/src/consensus.rs */ + test_agave_collect_vote_lockouts_sums( wksp ); + + fd_halt(); + return 0; +} diff --git a/src/choreo/tower/fd_tower.c b/src/choreo/tower/fd_tower.c index 3e598a551b..234643ff1a 100644 --- a/src/choreo/tower/fd_tower.c +++ b/src/choreo/tower/fd_tower.c @@ -114,7 +114,7 @@ fd_tower_lockout_check( fd_tower_t const * tower, 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 )); + FD_LOG_DEBUG(( "[fd_tower_lockout_check] ok? %d. top: (slot: %lu, conf: %lu). switch: %lu.", lockout_check, vote->slot, vote->conf, slot )); return lockout_check; } @@ -271,7 +271,7 @@ fd_tower_threshold_check( fd_tower_t const * tower, } 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 )->slot, threshold_slot, threshold_pct * 100.0 )); + FD_LOG_DEBUG(( "[%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; } diff --git a/src/choreo/tower/test_tower.c b/src/choreo/tower/test_tower.c index 77ab07e38e..691c08e0a6 100644 --- a/src/choreo/tower/test_tower.c +++ b/src/choreo/tower/test_tower.c @@ -81,10 +81,171 @@ test_tower_vote( void ) { fd_tower_delete( fd_tower_leave( tower ) ); } + +#include "../../util/wksp/fd_wksp.h" +#include "../../flamenco/runtime/program/fd_vote_program.c" + +void +check_lockouts( fd_tower_t * tower ) { + ulong i=0; + for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower ); + !fd_tower_votes_iter_done_rev( tower, iter ); + iter = fd_tower_votes_iter_prev( tower, iter ) ) { + ulong num_votes = fd_ulong_checked_sub_expect( fd_tower_votes_cnt( tower ), + i++, "`i` is less than `vote_state.votes.len()`" ); + + ulong min_lockout = fd_min( fd_tower_votes_iter_ele( tower, iter )->conf, MAX_LOCKOUT_HISTORY ); + FD_TEST( (ulong)pow( INITIAL_LOCKOUT, (double)min_lockout )==(ulong)pow( INITIAL_LOCKOUT, (double)(num_votes) ) ); + } +} + +ulong +last_locked_out_slot_wrapped( fd_tower_vote_t * lockout ) { + fd_vote_lockout_t converted={ .slot=lockout->slot, .confirmation_count=(uint)lockout->conf }; + return last_locked_out_slot( &converted ); +} + +void +test_tower_agave( void ) { + /* This function contains the 7 unit tests from tower_vote_state.rs in Agave. */ + + ulong page_cnt = 1; + char * _page_sz = "gigantic"; + ulong numa_idx = fd_shmem_numa_idx( 0 ); + fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), + page_cnt, + fd_shmem_cpu_idx( numa_idx ), + "wksp", + 0UL ); + FD_TEST( wksp ); + + ulong root = ULONG_MAX; + + /* test_basic_vote_state */ + { + fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) ); + + root = fd_tower_vote( tower, 1 ); + FD_TEST( 1==fd_tower_votes_cnt( tower ) ); + FD_TEST( 1==fd_tower_votes_peek_index( tower, 0 )->slot ); + FD_TEST( 1==fd_tower_votes_peek_index( tower, 0 )->conf ); + FD_TEST( ULONG_MAX==root ); + + root = fd_tower_vote( tower, 2 ); + FD_TEST( 2==fd_tower_votes_cnt( tower ) ); + FD_TEST( 1==fd_tower_votes_peek_index( tower, 0 )->slot ); + FD_TEST( 2==fd_tower_votes_peek_index( tower, 0 )->conf ); + FD_TEST( 2==fd_tower_votes_peek_index( tower, 1 )->slot ); + FD_TEST( 1==fd_tower_votes_peek_index( tower, 1 )->conf ); + } + + /* test_vote_lockout */ + { + fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) ); + + for( ulong i=0; iconf==expected_count ); + i++; + } + + ulong top_vote = fd_tower_votes_peek_head( tower )->slot; + ulong slot = last_locked_out_slot_wrapped( fd_tower_votes_peek_tail( tower ) ); + root = fd_tower_vote( tower, slot ); + FD_TEST( top_vote==root ); + + slot = last_locked_out_slot_wrapped( fd_tower_votes_peek_head( tower ) ); + root = fd_tower_vote( tower, slot ); + FD_TEST( 2==fd_tower_votes_cnt( tower ) ); + } + + /* test_vote_double_lockout_after_expiration */ + { + fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) ); + + for( ulong i=0; i<3; i++ ) root = fd_tower_vote( tower, i ); + check_lockouts( tower ); + + root = fd_tower_vote( tower, 2+INITIAL_LOCKOUT+1 ); + check_lockouts( tower ); + + root = fd_tower_vote( tower, 2+INITIAL_LOCKOUT+2 ); + check_lockouts( tower ); + + root = fd_tower_vote( tower, 2+INITIAL_LOCKOUT+3 ); + check_lockouts( tower ); + } + + /* test_expire_multiple_votes */ + { + fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) ); + + for( ulong i=0; i<3; i++ ) root = fd_tower_vote( tower, i ); + FD_TEST( 3==fd_tower_votes_peek_index( tower, 0 )->conf ); + + ulong expire_slot = last_locked_out_slot_wrapped( fd_tower_votes_peek_index( tower, 1 ) )+1; + root = fd_tower_vote( tower, expire_slot ); + FD_TEST( 2==fd_tower_votes_cnt( tower ) ); + + FD_TEST( 0==fd_tower_votes_peek_index( tower, 0 )->slot ); + FD_TEST( expire_slot==fd_tower_votes_peek_index( tower, 1 )->slot ); + + root = fd_tower_vote( tower, expire_slot+1 ); + FD_TEST( 3==fd_tower_votes_peek_index( tower, 0 )->conf ); + FD_TEST( 2==fd_tower_votes_peek_index( tower, 1 )->conf ); + FD_TEST( 1==fd_tower_votes_peek_index( tower, 2 )->conf ); + } + + /* test_multiple_root_progress */ + { + fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch ) ); + + for( ulong i=0; islot ); + FD_TEST( 7==fd_tower_votes_peek_index( tower, 1 )->slot ); + FD_TEST( root==ULONG_MAX ); // root is not updated yet (still the original root) + + for( ulong i=8; i<=MAX_LOCKOUT_HISTORY+8; i++ ) root = fd_tower_vote( tower, i ); + FD_TEST( root>original_root ); // root is updated now and is greater than the original root + FD_TEST( root==8 ); // based on running the test_tower_agave() function + } +} + int main( int argc, char ** argv ) { fd_boot( &argc, &argv ); test_tower_vote(); + test_tower_agave(); fd_halt(); return 0; }