Skip to content

Commit

Permalink
feat(ghost): xepoch voting and polish
Browse files Browse the repository at this point in the history
  • Loading branch information
lidatong committed Jan 9, 2025
1 parent 50b48f2 commit aa4bcd8
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 164 deletions.
2 changes: 1 addition & 1 deletion src/app/fdctl/run/tiles/fd_replay.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
184 changes: 90 additions & 94 deletions src/choreo/ghost/fd_ghost.c
Original file line number Diff line number Diff line change
Expand Up @@ -344,57 +344,61 @@ fd_ghost_head( fd_ghost_t const * ghost, fd_ghost_node_t const * node ) {
return head;
}

void
fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot ) {
static void
replay_vote( fd_ghost_t * ghost,
fd_pubkey_t const * key,
ulong last_stake,
ulong last_slot,
ulong stake,
ulong slot ) {
VER_INC;

FD_LOG_DEBUG(( "[%s] slot %lu, pubkey %s, stake %lu", __func__, slot, FD_BASE58_ENC_32_ALLOCA( &voter->key ), voter->stake ));
FD_LOG_DEBUG(( "[%s] key %s last_stake %lu last_slot %lu stake %lu slot %lu", __func__, FD_BASE58_ENC_32_ALLOCA( &key ), last_stake, last_slot, stake, slot ));

fd_ghost_node_map_t * node_map = fd_ghost_node_map( ghost );
fd_ghost_node_t * node_pool = fd_ghost_node_pool( ghost );
ulong vote = voter->replay_vote;
fd_ghost_node_t const * root = fd_ghost_root( ghost );

#if FD_GHOST_USE_HANDHOLDING
if( FD_UNLIKELY( slot < root->slot ) ) FD_LOG_ERR(( "[%s] illegal argument. vote slot: %lu, root: %lu", __func__, slot, root->slot ));
#endif

/* Return early if the vote slot <= the voter's last tower vote. It is
important that the vote slots are monotonically increasing, because
the order we receive blocks is non-deterministic (due to network
propagation variance), so we may process forks in a different order
from the sender of this vote.
/* Return early if the vote slot <= the voter's last vote slot. It
is important that the vote slots are monotonically increasing,
because the order we receive blocks is non-deterministic (due to
network propagation variance), so we may process forks in a
different order from the sender of this vote.
For example, if a validator votes on A then switches to B, we might
instead process B then A. In this case, the validator's tower on B
would contain a strictly higher vote slot than A (due to lockout),
so we would observe while processing A, that the vote slot < the
last vote slot we have saved for that validator. */

if( FD_UNLIKELY( vote != FD_SLOT_NULL && slot <= vote ) ) return;
if( FD_UNLIKELY( last_slot != FD_SLOT_NULL && slot <= last_slot ) ) return;

/* LMD-rule: subtract the voter's stake from previous vote. */
/* LMD-rule: subtract the voter's stake from the last vote slot. */

if( FD_LIKELY( vote != FD_SLOT_NULL && vote >= root->slot ) ) {
FD_LOG_DEBUG(( "[%s] removing (%s, %lu, %lu)", __func__, FD_BASE58_ENC_32_ALLOCA( &voter->key ), voter->stake, vote ));
if( FD_LIKELY( last_slot != FD_SLOT_NULL && last_slot >= root->slot ) ) {
FD_LOG_DEBUG(( "[%s] removing (%s, %lu, %lu)", __func__, FD_BASE58_ENC_32_ALLOCA( key ), last_stake, last_slot ));

fd_ghost_node_t * node = fd_ghost_node_map_ele_query( node_map, &vote, NULL, node_pool );
fd_ghost_node_t * node = fd_ghost_node_map_ele_query( node_map, &last_slot, NULL, node_pool );
#if FD_GHOST_USE_HANDHOLDING
if( FD_UNLIKELY( !node ) ) FD_LOG_ERR(( "missing ghost node" )); /* slot must be in ghost. */
#endif

#if FD_GHOST_USE_HANDHOLDING
int cf = __builtin_usubl_overflow( node->replay_stake, voter->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 ));
int cf = __builtin_usubl_overflow( node->replay_stake, last_stake, &node->replay_stake );
if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] sub overflow. node stake %lu voter stake %lu", __func__, node->replay_stake, last_stake ));
#else
node->replay_stake -= voter->stake;
#endif

fd_ghost_node_t * ancestor = node;
while( ancestor ) {
cf = __builtin_usubl_overflow( ancestor->weight, voter->stake, &ancestor->weight );
cf = __builtin_usubl_overflow( ancestor->weight, last_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 ));
if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] sub overflow. ancestor->weight %lu latest_vote->stake %lu", __func__, ancestor->weight, last_stake ));
#else
ancestor_weight -= voter->stake;
#endif
Expand All @@ -405,34 +409,63 @@ fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot ) {
/* Add voter's stake to the ghost node keyed by `slot`. Propagate the
vote stake up the ancestry. */

FD_LOG_DEBUG(( "[%s] adding (%s, %lu, %lu)", __func__, FD_BASE58_ENC_32_ALLOCA( &voter->key ), voter->stake, slot ));
FD_LOG_DEBUG(( "[%s] adding (%s, %lu, %lu)", __func__, FD_BASE58_ENC_32_ALLOCA( key ), stake, slot ));

fd_ghost_node_t * node = fd_ghost_node_map_ele_query( node_map, &slot, NULL, node_pool );
#if FD_GHOST_USE_HANDHOLDING
if( FD_UNLIKELY( !node ) ) FD_LOG_ERR(( "missing ghost node" )); /* slot must be in ghost. */
#endif

#if FD_GHOST_USE_HANDHOLDING
int cf = __builtin_uaddl_overflow( node->replay_stake, voter->stake, &node->replay_stake );
if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] add overflow. node->stake %lu latest_vote->stake %lu", __func__, node->replay_stake, voter->stake ));
int cf = __builtin_uaddl_overflow( node->replay_stake, stake, &node->replay_stake );
if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] add overflow. node->stake %lu latest_vote->stake %lu", __func__, node->replay_stake, stake ));
#else
node->replay_stake += voter->stake;
#endif

fd_ghost_node_t * ancestor = node;
while( ancestor ) {
#if FD_GHOST_USE_HANDHOLDING
int cf = __builtin_uaddl_overflow( ancestor->weight, voter->stake, &ancestor->weight );
if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] add overflow. ancestor->weight %lu latest_vote->stake %lu", __func__, ancestor->weight, voter->stake ));
int cf = __builtin_uaddl_overflow( ancestor->weight, stake, &ancestor->weight );
if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] add overflow. ancestor->weight %lu latest_vote->stake %lu", __func__, ancestor->weight, stake ));
#else
ancestor_weight += voter->stake;
#endif
ancestor = fd_ghost_node_pool_ele( node_pool, ancestor->parent_idx );
}

}

void
fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot ) {
replay_vote( ghost, &voter->key, voter->stake, voter->replay_vote, voter->stake, slot );
voter->replay_vote = slot; /* update the cached replay vote slot on voter */
}

void
fd_ghost_prev_epoch_replay_vote( fd_ghost_t * ghost,
fd_voter_t * curr,
fd_voter_t * prev,
ulong slot ) {
#if FD_GHOST_USE_HANDHOLDING
FD_TEST( 0 == memcmp( &prev->key, &curr->key, sizeof(fd_pubkey_t) ) ); /* caller error */
#endif

replay_vote( ghost, &curr->key, curr->stake, curr->replay_vote, prev->stake, slot );
}

void
fd_ghost_next_epoch_replay_vote( fd_ghost_t * ghost,
fd_voter_t * curr,
fd_voter_t * next,
ulong slot ) {
#if FD_GHOST_USE_HANDHOLDING
FD_TEST( 0 == memcmp( &next->key, &curr->key, sizeof(fd_pubkey_t) ) ); /* caller error */
#endif

replay_vote( ghost, &curr->key, curr->stake, curr->replay_vote, next->stake, slot );
}

void
fd_ghost_gossip_vote( FD_PARAM_UNUSED fd_ghost_t * ghost,
FD_PARAM_UNUSED fd_voter_t * voter,
Expand Down Expand Up @@ -471,54 +504,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
Expand All @@ -528,7 +543,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. */
Expand All @@ -537,18 +551,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 );
Expand All @@ -565,40 +573,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
Expand Down Expand Up @@ -670,8 +666,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" );
}
Loading

0 comments on commit aa4bcd8

Please sign in to comment.