From 71da0ffbd2a0a9fc3c72c31b13ff1df20f9ef776 Mon Sep 17 00:00:00 2001 From: Charles Li Date: Wed, 8 Jan 2025 16:38:10 +0000 Subject: [PATCH] fix(ghost): voting xepoch & polish --- src/app/fdctl/run/tiles/fd_replay.c | 2 +- src/choreo/epoch/fd_epoch.c | 4 +- src/choreo/epoch/test_epoch.c | 23 ++++ src/choreo/ghost/fd_ghost.c | 110 ++++++---------- src/choreo/ghost/fd_ghost.h | 62 ++++++---- src/choreo/ghost/test_ghost.c | 186 ++++++++++++++-------------- src/choreo/tower/fd_tower.c | 2 +- src/choreo/voter/fd_voter.h | 3 +- 8 files changed, 197 insertions(+), 195 deletions(-) create mode 100644 src/choreo/epoch/test_epoch.c diff --git a/src/app/fdctl/run/tiles/fd_replay.c b/src/app/fdctl/run/tiles/fd_replay.c index fc38c445f9..7d9650908e 100644 --- a/src/app/fdctl/run/tiles/fd_replay.c +++ b/src/app/fdctl/run/tiles/fd_replay.c @@ -1392,7 +1392,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_ghost_print( ctx->ghost, ctx->epoch->total_stake, fd_ghost_root( ctx->ghost ) ); 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 ); diff --git a/src/choreo/epoch/fd_epoch.c b/src/choreo/epoch/fd_epoch.c index 031757829c..aa8e6548ba 100644 --- a/src/choreo/epoch/fd_epoch.c +++ b/src/choreo/epoch/fd_epoch.c @@ -131,8 +131,8 @@ fd_epoch_init( fd_epoch_t * epoch, fd_epoch_bank_t const * epoch_bank ) { FD_TEST( fd_epoch_voters_query( epoch_voters, voter->key, NULL ) ); #endif - voter->stake = curr->elem.stake; - + voter->prev_stake = 0UL; + voter->stake = curr->elem.stake; voter->replay_vote = FD_SLOT_NULL; voter->gossip_vote = FD_SLOT_NULL; voter->rooted_vote = FD_SLOT_NULL; diff --git a/src/choreo/epoch/test_epoch.c b/src/choreo/epoch/test_epoch.c new file mode 100644 index 0000000000..8e060b4598 --- /dev/null +++ b/src/choreo/epoch/test_epoch.c @@ -0,0 +1,23 @@ +#include "fd_epoch.h" +#include + +fd_epoch_t * +epoch( fd_wksp_t * wksp, ulong total_stake, ulong voter_cnt, ... ) { + 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 ); + + va_list ap; + va_start( ap, voter_cnt ); + for( ulong i = 0; i < voter_cnt; i++ ) { + fd_pubkey_t key = va_arg( ap, fd_pubkey_t ); + fd_voter_t * voter = fd_epoch_voters_insert( fd_epoch_voters( epoch ), key ); + voter->stake = va_arg( ap, ulong ); + voter->replay_vote = FD_SLOT_NULL; + } + va_end( ap ); + + epoch->total_stake = total_stake; + return epoch; +} diff --git a/src/choreo/ghost/fd_ghost.c b/src/choreo/ghost/fd_ghost.c index 3e7ff43162..77d24b3cc7 100644 --- a/src/choreo/ghost/fd_ghost.c +++ b/src/choreo/ghost/fd_ghost.c @@ -384,7 +384,7 @@ fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot ) { #endif #if FD_GHOST_USE_HANDHOLDING - int cf = __builtin_usubl_overflow( node->replay_stake, voter->stake, &node->replay_stake ); + int cf = __builtin_usubl_overflow( node->replay_stake, voter->prev_stake, &node->replay_stake ); if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] sub overflow. node stake %lu voter stake %lu", __func__, node->replay_stake, voter->stake )); #else node->replay_stake -= voter->stake; @@ -392,7 +392,7 @@ fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot ) { fd_ghost_node_t * ancestor = node; while( ancestor ) { - cf = __builtin_usubl_overflow( ancestor->weight, voter->stake, &ancestor->weight ); + cf = __builtin_usubl_overflow( ancestor->weight, voter->prev_stake, &ancestor->weight ); #if FD_GHOST_USE_HANDHOLDING if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] sub overflow. ancestor->weight %lu latest_vote->stake %lu", __func__, ancestor->weight, voter->stake )); #else @@ -430,6 +430,7 @@ fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot ) { ancestor = fd_ghost_node_pool_ele( node_pool, ancestor->parent_idx ); } + voter->prev_stake = voter->stake; voter->replay_vote = slot; /* update the cached replay vote slot on voter */ } @@ -471,54 +472,36 @@ fd_ghost_rooted_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong root ) { node->rooted_stake += voter->stake; } -fd_ghost_node_t const * +void fd_ghost_publish( fd_ghost_t * ghost, ulong slot ) { - FD_LOG_NOTICE(( "[%s] slot %lu", __func__, slot )); VER_INC; + FD_LOG_DEBUG(( "[%s] slot %lu", __func__, slot )); + fd_ghost_node_map_t * node_map = fd_ghost_node_map( ghost ); fd_ghost_node_t * node_pool = fd_ghost_node_pool( ghost ); fd_ghost_node_t const * root_node = fd_ghost_root( ghost ); ulong null_idx = fd_ghost_node_pool_idx_null( node_pool ); -#if FD_GHOST_USE_HANDHOLDING - if( FD_UNLIKELY( slot < root_node->slot ) ) { - FD_LOG_WARNING(( "[fd_ghost_publish] trying to publish slot %lu older than ghost->root %lu.", - slot, - root_node->slot )); - return NULL; - } - if( FD_UNLIKELY( slot == root_node->slot ) ) { - FD_LOG_WARNING(( "[fd_ghost_publish] publishing same slot %lu as ghost->root %lu.", - slot, - root_node->slot )); - return NULL; - } -#endif - - // new root - fd_ghost_node_t * root = fd_ghost_node_map_ele_query( node_map, - &slot, - NULL, - node_pool ); - -#if FD_GHOST_USE_HANDHOLDING - if( FD_UNLIKELY( !root ) ) { - FD_LOG_ERR(( "[fd_ghost_publish] publish slot %lu not found in ghost", slot )); - } + #if FD_GHOST_USE_HANDHOLDING + FD_TEST( slot > root_node->slot ); /* caller error */ + #endif -#endif + fd_ghost_node_t * root = fd_ghost_node_map_ele_query( node_map, &slot, NULL, node_pool ); /* new root */ + #if FD_GHOST_USE_HANDHOLDING + FD_TEST( root ); /* caller error */ + #endif /* First, remove the previous root, and add it to the prune list. In this context, head is the list head (not to be confused with the ghost head.) */ - fd_ghost_node_t * head = fd_ghost_node_map_ele_remove( node_map, - &root_node->slot, - NULL, - node_pool ); - head->next = fd_ghost_node_pool_idx_null( node_pool ); + fd_ghost_node_t * head = fd_ghost_node_map_ele_remove( node_map, &root_node->slot, NULL, node_pool ); + #if FD_GHOST_USE_HANDHOLDING + FD_TEST( head ); /* memory corruption */ + #endif + head->next = null_idx; fd_ghost_node_t * tail = head; /* Second, BFS down the tree, adding nodes to the prune queue except @@ -528,7 +511,6 @@ fd_ghost_publish( fd_ghost_t * ghost, ulong slot ) { while( head ) { fd_ghost_node_t * child = fd_ghost_node_pool_ele( node_pool, head->child_idx ); - while( FD_LIKELY( child ) ) { /* Do not prune the new root. */ @@ -537,18 +519,12 @@ fd_ghost_publish( fd_ghost_t * ghost, ulong slot ) { /* Remove the child from the map and push the child onto the list. */ - tail->next = fd_ghost_node_map_idx_remove( node_map, - &child->slot, - fd_ghost_node_pool_idx_null( node_pool ), - node_pool ); -#if FD_GHOST_USE_HANDHOLDING - if( FD_UNLIKELY( tail->next == fd_ghost_node_pool_idx_null( node_pool ) ) ) { - FD_LOG_ERR(( "Failed to remove child. Child must exist given the while condition. " - "Possible memory corruption or data race." )); - } -#endif + tail->next = fd_ghost_node_map_idx_remove( node_map, &child->slot, null_idx, node_pool ); + #if FD_GHOST_USE_HANDHOLDING + FD_TEST( tail->next != null_idx ); /* memory corruption */ + #endif tail = fd_ghost_node_pool_ele( node_pool, tail->next ); - tail->next = fd_ghost_node_pool_idx_null( node_pool ); + tail->next = null_idx; } child = fd_ghost_node_pool_ele( node_pool, child->sibling_idx ); @@ -565,40 +541,28 @@ fd_ghost_publish( fd_ghost_t * ghost, ulong slot ) { root->parent_idx = null_idx; ghost->root_idx = fd_ghost_node_map_idx_query( node_map, &slot, null_idx, node_pool ); - - return root; } fd_ghost_node_t const * fd_ghost_gca( fd_ghost_t const * ghost, ulong slot1, ulong slot2 ) { - fd_ghost_node_t const * node_pool = fd_ghost_node_pool_const( ghost ); - fd_ghost_node_t const * node1 = fd_ghost_query( ghost, slot1 ); - fd_ghost_node_t const * node2 = fd_ghost_query( ghost, slot2 ); - -#if FD_GHOST_USE_HANDHOLDING - if( FD_UNLIKELY( !node1 ) ) { - FD_LOG_WARNING(( "slot1 %lu is missing from ghost", slot1 )); - return NULL; - } + fd_ghost_node_t const * anc1 = fd_ghost_query( ghost, slot1 ); + fd_ghost_node_t const * anc2 = fd_ghost_query( ghost, slot2 ); - if( FD_UNLIKELY( !node2 ) ) { - FD_LOG_WARNING(( "slot2 %lu is missing from ghost", slot2 )); - return NULL; - } -#endif + #if FD_GHOST_USE_HANDHOLDING + /* caller error */ + FD_TEST( anc1 ); + FD_TEST( anc2 ); + #endif /* Find the greatest common ancestor. */ - while( node1 && node2 ) { - if( FD_UNLIKELY( node1->slot == node2->slot ) ) return node1; - if( node1->slot > node2->slot ) { - node1 = fd_ghost_node_pool_ele_const( node_pool, node1->parent_idx ); - } else { - node2 = fd_ghost_node_pool_ele_const( node_pool, node2->parent_idx ); - } + while( FD_LIKELY( anc1 && anc2 ) ) { + if( FD_UNLIKELY( anc1->slot == anc2->slot ) ) return anc1; /* found */ + if( anc1->slot > anc2->slot ) anc1 = fd_ghost_parent( ghost, anc1 ); /* move anc1 up the tree */ + else anc2 = fd_ghost_parent( ghost, anc2 ); /* move anc2 up the tree */ } - FD_LOG_ERR(( "Unable to find GCA. Is this a valid ghost?" )); + FD_LOG_ERR(( "[%s] invariant violation: unable to find gca", __func__ )); /* memory corruption */ } int @@ -670,8 +634,8 @@ print( fd_ghost_t const * ghost, fd_ghost_node_t const * node, int space, const } void -fd_ghost_print( fd_ghost_t const * ghost, fd_epoch_t const * epoch, fd_ghost_node_t const * node ) { +fd_ghost_print( fd_ghost_t const * ghost, ulong total_stake, fd_ghost_node_t const * node ) { FD_LOG_NOTICE( ( "\n\n[Ghost]" ) ); - print( ghost, node, 0, "", epoch->total_stake ); + print( ghost, node, 0, "", total_stake ); printf( "\n\n" ); } diff --git a/src/choreo/ghost/fd_ghost.h b/src/choreo/ghost/fd_ghost.h index b56aa4dbdc..8b200b8e85 100644 --- a/src/choreo/ghost/fd_ghost.h +++ b/src/choreo/ghost/fd_ghost.h @@ -317,9 +317,9 @@ fd_ghost_insert( fd_ghost_t * ghost, ulong parent_slot, ulong slot ); /* fd_ghost_replay_vote votes for slot, adding pubkey's stake to the `stake` field for slot and to the `weight` field for both slot and - slot's ancestors. If pubkey has previously voted, pubkey's stake is - also subtracted from `weight` for its previous vote slot and its - ancestors. + slot's ancestors. If pubkey has previously voted, pubkey's stake as + of the previous vote slot's epoch is also subtracted from `weight` + for its previous vote slot and its ancestors. Assumes slot is present in ghost (if handholding is enabled, explicitly checks and errors). Returns the ghost node keyed by slot. @@ -331,28 +331,34 @@ fd_ghost_insert( fd_ghost_t * ghost, ulong parent_slot, ulong slot ); void fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot ); +/* fd_ghost_next_epoch_replay_vote is fd_ghost_replay_vote but the voter + is voting for a slot in the next epoch. */ + +void +fd_ghost_next_epoch_replay_vote( fd_ghost_t * ghost, + fd_voter_t * curr, + fd_voter_t * next, + ulong slot ); + /* fd_ghost_gossip_vote adds stake amount to the gossip_stake field of - slot. + the node keyed by slot. Assumes slot is present in ghost (if handholding is enabled, explicitly checks and errors). Returns the ghost node keyed by slot. - Unlike fd_ghost_replay_vote, this stake is not propagated to - the weight field for slot and slot's ancestors. It is only counted + Unlike fd_ghost_replay_vote, this stake is not propagated to the + weight field for slot and slot's ancestors. It is only counted towards slot itself, as gossip votes are only used for optimistic - confirmation and not fork choice. */ + confirmation and not fork choice. The LMD-rule also applies to + gossip votes. */ void fd_ghost_gossip_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot ); /* fd_ghost_rooted_vote adds stake amount to the rooted_stake field of - slot. - - Assumes slot is present in ghost (if handholding is enabled, - explicitly checks and errors). Returns the ghost node keyed by slot. - - Note rooting a slot implies rooting its ancestor, but ghost does not - explicitly track this. */ + slot. Assumes slot is present in ghost (if handholding is enabled, + explicitly checks and errors). Rooting doesn't incorporate the + LMD-rule: a voter's stake can count towards multiple root votes. */ void fd_ghost_rooted_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong root ); @@ -360,9 +366,9 @@ fd_ghost_rooted_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong root ); /* fd_ghost_publish publishes slot as the new ghost root, setting the subtree beginning from slot as the new ghost tree (ie. slot and all its descendants). Prunes all nodes not in slot's ancestry. Assumes - slot is present in ghost. Returns the new root. */ + slot is present in ghost. */ -fd_ghost_node_t const * +void fd_ghost_publish( fd_ghost_t * ghost, ulong slot ); /* Misc */ @@ -375,26 +381,30 @@ fd_ghost_publish( fd_ghost_t * ghost, ulong slot ); FD_FN_PURE int fd_ghost_verify( fd_ghost_t const * ghost ); -/* fd_ghost_print pretty-prints a formatted ghost tree. Printing begins - from `node` (it will appear as the root in the print output). +/* fd_ghost_print pretty-prints a formatted ghost tree beginning from + `node`. If `total` is non-zero nodes will print as a percentage + `node->weight / total` otherwise will print `node->weight`. + + The most straightforward way to print a ghost from the root with + percentages: - The most straightforward and commonly used printing pattern is: - `fd_ghost_print( ghost, fd_ghost_root( ghost ) )` + ``` + fd_ghost_print( ghost, epoch->total_stake, fd_ghost_root( ghost ) ) + ``` - This would print ghost beginning from the root. + This would print ghost beginning from the root as percentages. - Alternatively, caller can print a more localized view, for example - starting from the grandparent of the most recently executed slot: + Another example is printing the ghost starting from the grandparent + of the most recently executed slot without percentages: ``` - fd_ghost_node_t const * node = fd_ghost_query( slot ); - fd_ghost_print( ghost, fd_ghost_parent( fd_ghost_parent( node ) ) ) + fd_ghost_print( fd_ghost_query( slot ), 0, fd_ghost_parent( ghost, fd_ghost_parent( ghost, node ) ) ); ``` Callers should add null-checks as appropriate in actual usage. */ void -fd_ghost_print( fd_ghost_t const * ghost, fd_epoch_t const * epoch, fd_ghost_node_t const * node ); +fd_ghost_print( fd_ghost_t const * ghost, ulong total, fd_ghost_node_t const * node ); FD_PROTOTYPES_END diff --git a/src/choreo/ghost/test_ghost.c b/src/choreo/ghost/test_ghost.c index 764d48033a..9c68e204ba 100644 --- a/src/choreo/ghost/test_ghost.c +++ b/src/choreo/ghost/test_ghost.c @@ -1,12 +1,11 @@ #include "fd_ghost.h" -#include "../epoch/fd_epoch.h" #include -#define INSERT( c, p ) \ - slots[i] = c; \ - parent_slots[i] = p; \ - fd_ghost_insert( ghost, parent_slots[i], slots[i] ); \ +#define INSERT(c,p) \ + slots[i] = c; \ + parent_slots[i] = p; \ + fd_ghost_insert( ghost, parent_slots[i], slots[i] ); \ i++; fd_ghost_node_t * @@ -17,27 +16,6 @@ query_mut( fd_ghost_t * ghost, ulong slot ) { return fd_ghost_node_map_ele_query( node_map, &slot, NULL, node_pool ); } -fd_epoch_t * -mock_epoch( fd_wksp_t * wksp, ulong total_stake, ulong voter_cnt, ... ) { - 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 ); - - va_list ap; - va_start( ap, voter_cnt ); - for( ulong i = 0; i < voter_cnt; i++ ) { - fd_pubkey_t key = va_arg( ap, fd_pubkey_t ); - fd_voter_t * voter = fd_epoch_voters_insert( fd_epoch_voters( epoch ), key ); - voter->stake = va_arg( ap, ulong ); - voter->replay_vote = FD_SLOT_NULL; - } - va_end( ap ); - - epoch->total_stake = total_stake; - return epoch; -} - /* slot 0 | @@ -50,6 +28,7 @@ mock_epoch( fd_wksp_t * wksp, ulong total_stake, ulong voter_cnt, ... ) { | slot 6 */ + void test_ghost_simple( fd_wksp_t * wksp ) { ulong node_max = 8; @@ -72,17 +51,24 @@ test_ghost_simple( fd_wksp_t * wksp ) { INSERT( 6, 5 ); FD_TEST( !fd_ghost_verify( ghost ) ); - fd_pubkey_t key = { .key = { 1 } }; - fd_epoch_t * epoch = mock_epoch( wksp, 10, 1, key, 1 ); - fd_voter_t * voter = fd_epoch_voters_query( fd_epoch_voters( epoch ), key, NULL ); + fd_voter_t voter[1] = { { .key = {{1}}, .stake = 1 } }; - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, 10, fd_ghost_root( ghost ) ); fd_ghost_replay_vote( ghost, voter, 2 ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + FD_TEST( fd_ghost_query( ghost, 2 )->replay_stake == 1 ); + FD_TEST( fd_ghost_query( ghost, 2 )->weight == 1 ); + FD_TEST( fd_ghost_query( ghost, 0 )->weight == 1 ); + FD_TEST( fd_ghost_query( ghost, 1 )->weight == 1 ); + FD_TEST( fd_ghost_query( ghost, 2 )->weight == 1 ); + // fd_ghost_print( ghost, 10, fd_ghost_root( ghost ) ); fd_ghost_replay_vote( ghost, voter, 3 ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + FD_TEST( fd_ghost_query( ghost, 3 )->replay_stake == 1 ); + FD_TEST( fd_ghost_query( ghost, 2 )->weight == 0 ); + FD_TEST( fd_ghost_query( ghost, 0 )->weight == 1 ); + FD_TEST( fd_ghost_query( ghost, 1 )->weight == 1 ); + FD_TEST( fd_ghost_query( ghost, 3 )->weight == 1 ); + // fd_ghost_print( ghost, 10, fd_ghost_root( ghost ) ); - fd_wksp_free_laddr( fd_epoch_delete( fd_epoch_leave( epoch ) ) ); fd_wksp_free_laddr( fd_ghost_delete( fd_ghost_leave( ghost ) ) ); } @@ -104,6 +90,7 @@ test_ghost_simple( fd_wksp_t * wksp ) { | slot 4 */ + void test_ghost_publish_left( fd_wksp_t * wksp ) { ulong node_max = 8; @@ -128,12 +115,10 @@ test_ghost_publish_left( fd_wksp_t * wksp ) { INSERT( 6, 5 ); FD_TEST( !fd_ghost_verify( ghost ) ); - fd_pubkey_t pk1 = { { 1 } }; - fd_epoch_t * epoch = mock_epoch( wksp, 2, 1, pk1, 1 ); - fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL ); + fd_voter_t v1[1] = { { .key = {{1}}, .stake = 1 } }; fd_ghost_replay_vote( ghost, v1, 2 ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, epoch->total_stake, fd_ghost_root( ghost ) ); FD_TEST( !fd_ghost_verify( ghost ) ); fd_ghost_replay_vote( ghost, v1, 3 ); @@ -141,24 +126,15 @@ test_ghost_publish_left( fd_wksp_t * wksp ) { FD_TEST( node2 ); FD_TEST( !fd_ghost_verify( ghost ) ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, epoch->total_stake, fd_ghost_root( ghost ) ); fd_ghost_publish( ghost, 2 ); fd_ghost_node_t const * root = fd_ghost_root( ghost ); FD_TEST( root->slot == 2 ); - - fd_ghost_node_map_t * node_map = fd_ghost_node_map( ghost ); - for( fd_ghost_node_map_iter_t iter = fd_ghost_node_map_iter_init( node_map, node_pool ); - !fd_ghost_node_map_iter_done( iter, node_map, node_pool ); - iter = fd_ghost_node_map_iter_next( iter, node_map, node_pool ) ) { - fd_ghost_node_t const * node = fd_ghost_node_map_iter_ele( iter, node_map, node_pool ); - FD_LOG_NOTICE(("slot: %lu", node->slot)); - } - FD_TEST( !fd_ghost_verify( ghost ) ); FD_TEST( fd_ghost_node_pool_ele( node_pool, root->child_idx )->slot == 4 ); FD_TEST( fd_ghost_node_pool_free( node_pool ) == node_max - 2 ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, epoch->total_stake, fd_ghost_root( ghost ) ); fd_wksp_free_laddr( mem ); } @@ -183,6 +159,7 @@ test_ghost_publish_left( fd_wksp_t * wksp ) { | slot 6 */ + void test_ghost_publish_right( fd_wksp_t * wksp ) { ulong node_max = 8; @@ -207,9 +184,7 @@ test_ghost_publish_right( fd_wksp_t * wksp ) { INSERT( 6, 5 ); FD_TEST( !fd_ghost_verify( ghost ) ); - fd_pubkey_t pk1 = { { 1 } }; - fd_epoch_t * epoch = mock_epoch( wksp, 2, 1, pk1, 1 ); - fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL ); + fd_voter_t v1[1] = { { .key = {{1}}, .stake = 1 } }; fd_ghost_replay_vote( ghost, v1, 2 ); FD_TEST( !fd_ghost_verify( ghost ) ); @@ -219,7 +194,7 @@ test_ghost_publish_right( fd_wksp_t * wksp ) { fd_ghost_node_t const * node3 = fd_ghost_query( ghost, 3 ); FD_TEST( node3 ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, epoch->total_stake, fd_ghost_root( ghost ) ); fd_ghost_publish( ghost, 3 ); FD_TEST( !fd_ghost_verify( ghost ) ); @@ -228,7 +203,7 @@ test_ghost_publish_right( fd_wksp_t * wksp ) { FD_TEST( fd_ghost_node_pool_ele( node_pool, root->child_idx )->slot == 5 ); FD_TEST( fd_ghost_child( ghost, fd_ghost_child( ghost, root ) )->slot == 6 ); FD_TEST( fd_ghost_node_pool_free( node_pool ) == node_max - 3 ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, epoch->total_stake, fd_ghost_root( ghost ) ); fd_wksp_free_laddr( mem ); } @@ -257,8 +232,7 @@ test_ghost_gca( fd_wksp_t * wksp ) { INSERT( 6, 5 ); FD_TEST( !fd_ghost_verify( ghost ) ); - fd_epoch_t * epoch = mock_epoch( wksp, 0, 0 ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, 0, fd_ghost_root( ghost ) ); FD_TEST( fd_ghost_gca( ghost, 0, 0 )->slot == 0 ); @@ -310,8 +284,6 @@ test_ghost_print( fd_wksp_t * wksp ) { ulong parent_slots[node_max]; ulong i = 0; - fd_epoch_t * epoch = mock_epoch( wksp, 300, 0 ); - fd_ghost_init( ghost, 268538758 ); INSERT( 268538759, 268538758 ); INSERT( 268538760, 268538759 ); @@ -337,8 +309,7 @@ test_ghost_print( fd_wksp_t * wksp ) { node->weight = 10; FD_TEST( !fd_ghost_verify( ghost ) ); - fd_ghost_node_t const * grandparent = fd_ghost_parent( ghost, fd_ghost_parent( ghost, fd_ghost_query( ghost, 268538760 ) ) ); - fd_ghost_print( ghost, epoch, grandparent ); + // fd_ghost_print( ghost, 300, fd_ghost_parent( ghost, fd_ghost_parent( ghost, fd_ghost_query( ghost, 268538760 ) ) ) ); fd_wksp_free_laddr( mem ); } @@ -363,11 +334,8 @@ test_ghost_head( fd_wksp_t * wksp ){ ulong parent_slots[node_max]; ulong i = 0; - fd_pubkey_t pk1 = { { 1 } }; - fd_pubkey_t pk2 = { { 2 } }; - fd_epoch_t * epoch = mock_epoch( wksp, 150, 2, pk1, 50, pk2, 100 ); - fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL ); - fd_voter_t * v2 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk2, NULL ); + fd_voter_t v1[1] = { { .key = {{1}}, .stake = 50 } }; + fd_voter_t v2[1] = { { .key = {{2}}, .stake = 100 } }; fd_ghost_init( ghost, 10 ); INSERT( 11, 10 ); @@ -389,7 +357,7 @@ test_ghost_head( fd_wksp_t * wksp ){ fd_ghost_node_t const * head2 = fd_ghost_head( ghost, fd_ghost_root( ghost ) ); FD_TEST( head2->slot == 12 ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, epoch->total_stake, fd_ghost_root( ghost ) ); fd_wksp_free_laddr( mem ); } @@ -406,7 +374,6 @@ void test_ghost_vote_leaves( fd_wksp_t * wksp ){ fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, 0UL, node_max ) ); fd_ghost_init( ghost, 0 ); - fd_epoch_t * epoch = mock_epoch( wksp, 40, 0 ); /* make a full binary tree */ for( ulong i = 1; i < node_max - 1; i++){ @@ -421,7 +388,7 @@ void test_ghost_vote_leaves( fd_wksp_t * wksp ){ v.replay_vote = i; } - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, 40, fd_ghost_root( ghost ) ); ulong path[d]; ulong leaf = node_max - 2; @@ -464,7 +431,7 @@ void test_ghost_vote_leaves( fd_wksp_t * wksp ){ } FD_TEST( !fd_ghost_verify( ghost ) ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, epoch->total_stake, fd_ghost_root( ghost ) ); } void @@ -477,10 +444,7 @@ test_ghost_head_full_tree( fd_wksp_t * wksp ){ FD_TEST( mem ); fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, 0UL, node_max ) ); - fd_epoch_t * epoch = mock_epoch( wksp, 120, 0 ); - fd_ghost_init( ghost, 0 ); - FD_LOG_NOTICE(( "ghost node max: %lu", fd_ghost_node_pool_max( fd_ghost_node_pool( ghost ) ) )); for ( ulong i = 1; i < node_max - 1; i++ ) { fd_ghost_insert( ghost, (i-1)/2, i ); @@ -495,11 +459,9 @@ test_ghost_head_full_tree( fd_wksp_t * wksp ){ FD_TEST( !fd_ghost_verify( ghost ) ); - fd_ghost_print( ghost, epoch, fd_ghost_root( ghost ) ); + // fd_ghost_print( ghost, 120, fd_ghost_root( ghost ) ); fd_ghost_node_t const * head = fd_ghost_head( ghost, fd_ghost_root( ghost ) ); - FD_LOG_NOTICE(( "head slot %lu", head->slot )); - // head will always be rightmost node in this complete binary tree FD_TEST( head->slot == 14 ); @@ -527,11 +489,8 @@ test_rooted_vote( fd_wksp_t * wksp ){ FD_TEST( mem ); fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, 0UL, node_max ) ); - fd_pubkey_t pk1 = { .key = { 1 } }; - fd_pubkey_t pk2 = { .key = { 2 } }; - fd_epoch_t * epoch = mock_epoch( wksp, 120, 2, pk1, 20, pk2, 10 ); - fd_voter_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL ); - fd_voter_t * v2 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk2, NULL ); + fd_voter_t v1[1] = { { .key = {{1}}, .stake = 20 } }; + fd_voter_t v2[1] = { { .key = {{2}}, .stake = 10 } }; fd_ghost_init( ghost, 0 ); @@ -548,22 +507,67 @@ test_rooted_vote( fd_wksp_t * wksp ){ FD_TEST( !fd_ghost_verify( ghost ) ); } +void +test_ghost_xepoch_replay_vote( fd_wksp_t * wksp ) { + ulong node_max = 8; + + void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( node_max ), 1UL ); + FD_TEST( mem ); + + fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, 0UL, node_max ) ); + fd_ghost_node_t * node_pool = fd_wksp_laddr_fast( wksp, ghost->node_pool_gaddr ); + + ulong slots[fd_ghost_node_pool_max( node_pool )]; + ulong parent_slots[fd_ghost_node_pool_max( node_pool )]; + ulong i = 0; + + fd_ghost_init( ghost, 0 ); + INSERT( 1, 0 ); + INSERT( 2, 1 ); + INSERT( 3, 1 ); + INSERT( 4, 2 ); + INSERT( 5, 3 ); + INSERT( 6, 5 ); + FD_TEST( !fd_ghost_verify( ghost ) ); + + fd_voter_t voter[1] = { { .key = {{1}}, .stake = 1 } }; + + fd_ghost_replay_vote( ghost, voter, 2 ); + // fd_ghost_print( ghost, prev_epoch->total_stake, fd_ghost_root( ghost ) ); + FD_TEST( fd_ghost_query( ghost, 2 )->replay_stake == 1 ); + FD_TEST( fd_ghost_query( ghost, 0 )->weight == 1 ); + FD_TEST( fd_ghost_query( ghost, 1 )->weight == 1 ); + FD_TEST( fd_ghost_query( ghost, 2 )->weight == 1 ); + + voter->stake = 2; + fd_ghost_replay_vote( ghost, voter, 3 ); + // fd_ghost_print( ghost, prev_epoch->total_stake, fd_ghost_root( ghost ) ); + FD_TEST( fd_ghost_query( ghost, 2 )->weight == 0 ); + FD_TEST( fd_ghost_query( ghost, 3 )->replay_stake == 2 ); + FD_TEST( fd_ghost_query( ghost, 0 )->weight == 2 ); + FD_TEST( fd_ghost_query( ghost, 1 )->weight == 2 ); + FD_TEST( fd_ghost_query( ghost, 3 )->weight == 2 ); + + voter->stake = 3; + // fd_ghost_print( ghost, prev_epoch->total_stake, fd_ghost_root( ghost ) ); + FD_TEST( fd_ghost_query( ghost, 3 )->weight == 0 ); + FD_TEST( fd_ghost_query( ghost, 4 )->replay_stake == 3 ); + FD_TEST( fd_ghost_query( ghost, 0 )->weight == 3 ); + FD_TEST( fd_ghost_query( ghost, 1 )->weight == 3 ); + FD_TEST( fd_ghost_query( ghost, 2 )->weight == 3 ); + FD_TEST( fd_ghost_query( ghost, 4 )->weight == 3 ); + + fd_wksp_free_laddr( fd_ghost_delete( fd_ghost_leave( ghost ) ) ); +} + int main( int argc, char ** argv ) { fd_boot( &argc, &argv ); - ulong page_cnt = 1; - char * _page_sz = "gigantic"; - ulong numa_idx = fd_shmem_numa_idx( 0 ); - FD_LOG_NOTICE( ( "Creating workspace (--page-cnt %lu, --page-sz %s, --numa-idx %lu)", - page_cnt, - _page_sz, - numa_idx ) ); - 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 ); + 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 ); test_ghost_print( wksp ); diff --git a/src/choreo/tower/fd_tower.c b/src/choreo/tower/fd_tower.c index af0a6a4839..4e5f0a9623 100644 --- a/src/choreo/tower/fd_tower.c +++ b/src/choreo/tower/fd_tower.c @@ -301,7 +301,7 @@ fd_tower_reset_slot( fd_tower_t const * tower, fd_ghost_node_t const * vote_node = fd_ghost_query( ghost, vote->slot ); #if FD_TOWER_USE_HANDHOLDING if( FD_UNLIKELY( !vote_node ) ) { - fd_ghost_print( ghost, epoch, root ); + fd_ghost_print( ghost, epoch->total_stake, root ); FD_LOG_ERR(( "[%s] invariant violation: unable to find last tower vote slot %lu in ghost.", __func__, vote->slot )); } #endif diff --git a/src/choreo/voter/fd_voter.h b/src/choreo/voter/fd_voter.h index 1ae4f7476e..dabbf05136 100644 --- a/src/choreo/voter/fd_voter.h +++ b/src/choreo/voter/fd_voter.h @@ -25,7 +25,8 @@ struct fd_voter { /* IMPORTANT! The values below should only be modified by fd_epoch and fd_ghost. */ - ulong stake; /* voter's stake */ + ulong prev_stake; /* voter's stake as of the previous vote slot */ + ulong stake; /* voter's current stake (usually == prev stake) */ ulong replay_vote; /* cached read of last tower vote via replay */ ulong gossip_vote; /* cached read of last tower vote via gossip */ ulong rooted_vote; /* cached read of last tower root via replay */