Skip to content

Commit

Permalink
feat(choreo): add gossip vote support to ghost, stub out tower checks
Browse files Browse the repository at this point in the history
  • Loading branch information
lidatong committed May 14, 2024
1 parent 1d60058 commit 06819f6
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 140 deletions.
14 changes: 3 additions & 11 deletions src/choreo/bft/fd_bft.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
#include "../../flamenco/runtime/context/fd_exec_slot_ctx.h"
#include "../../flamenco/runtime/fd_acc_mgr.h"
#include "../../flamenco/runtime/fd_borrowed_account.h"
#include "../../flamenco/runtime/program/fd_program_util.h"
#include "../../flamenco/runtime/program/fd_vote_program.h"

#include "../fd_choreo_base.h"
#include "../ghost/fd_ghost.h"
#include "../tower/fd_tower.h"
#include "fd_bft.h"

#pragma GCC diagnostic ignored "-Wformat"
Expand Down Expand Up @@ -202,8 +193,9 @@ count_gossip_votes( fd_bft_t * bft, fd_latest_vote_t * gossip_votes, FD_PARAM_UN
continue;
}

// ulong stake = query_pubkey_stake( &vote->node_pubkey, epoch_stakes );
// fd_ghost_gossip_vote_upsert( bft->ghost, &slot_hash, &vote->node_pubkey, stake );
ulong stake = query_pubkey_stake( &vote->node_pubkey, epoch_stakes );
fd_ghost_gossip_vote_upsert( bft->ghost, &slot_hash, &vote->node_pubkey, stake );

}
}

Expand Down
19 changes: 10 additions & 9 deletions src/choreo/bft/fd_bft.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,19 @@
/* fd_bft implements Solana's Proof-of-Stake consensus protocol. */

struct fd_bft {
ulong snapshot_slot;
ulong epoch_stake; /* total amount of stake in the current epoch */
fd_tower_t tower; /* our local vote tower */
ulong snapshot_slot;
ulong epoch_stake; /* total amount of stake in the current epoch */

/* external joins, pointer don't need updating */

fd_acc_mgr_t * acc_mgr;
fd_blockstore_t * blockstore;
fd_commitment_t * commitment;
fd_forks_t * forks;
fd_ghost_t * ghost;
fd_valloc_t valloc;
fd_acc_mgr_t * acc_mgr;
fd_blockstore_t * blockstore;
fd_commitment_t * commitment;
fd_forks_t * forks;
fd_ghost_t * ghost;
fd_tower_t * tower;
fd_valloc_t valloc;
fd_vote_accounts_t * vote_accounts;
};
typedef struct fd_bft fd_bft_t;

Expand Down
1 change: 1 addition & 0 deletions src/choreo/fd_choreo.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
#include "commitment/fd_commitment.h" /* Includes fd_choreo_base.h */
#include "forks/fd_forks.h" /* Includes fd_choreo_base.h */
#include "ghost/fd_ghost.h" /* Includes fd_choreo_base.h */
#include "tower/fd_tower.h" /* Includes fd_choreo_base.h */

#endif /* HEADER_fd_src_choreo_fd_choreo_h */
112 changes: 81 additions & 31 deletions src/choreo/ghost/fd_ghost.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ fd_ghost_new( void * shmem, ulong node_max, ulong vote_max, ulong seed ) {
ghost->vote_pool = fd_ghost_vote_pool_new( (void *)laddr, vote_max );
laddr += fd_ghost_vote_pool_footprint( vote_max );

laddr = fd_ulong_align_up( laddr, fd_ghost_vote_map_align() );
ghost->vote_map = fd_ghost_vote_map_new( (void *)laddr, vote_max, seed );
laddr = fd_ulong_align_up( laddr, fd_ghost_vote_map_align() );
ghost->replay_vote_map = fd_ghost_vote_map_new( (void *)laddr, vote_max, seed );
laddr += fd_ghost_vote_map_footprint( vote_max );

laddr = fd_ulong_align_up( laddr, alignof( fd_ghost_t ) );
Expand Down Expand Up @@ -84,8 +84,8 @@ fd_ghost_join( void * shghost ) { /* process 1: 0xFA process 2: 0x2F */
ulong vote_max = fd_ghost_vote_pool_max( ghost->vote_pool );
laddr += fd_ghost_vote_pool_footprint( vote_max );

laddr = fd_ulong_align_up( laddr, fd_ghost_vote_map_align() );
ghost->vote_map = fd_ghost_vote_map_join( (void *)laddr );
laddr = fd_ulong_align_up( laddr, fd_ghost_vote_map_align() );
ghost->replay_vote_map = fd_ghost_vote_map_join( (void *)laddr );
laddr += fd_ghost_vote_map_footprint( fd_ghost_vote_map_ele_max() );

return ghost;
Expand Down Expand Up @@ -212,11 +212,11 @@ fd_ghost_node_query( fd_ghost_t * ghost, fd_slot_hash_t const * key ) {

void
fd_ghost_replay_vote_upsert( fd_ghost_t * ghost,
fd_slot_hash_t const * key,
fd_slot_hash_t const * slot_hash,
fd_pubkey_t const * pubkey,
ulong stake ) {
fd_ghost_node_t * node =
fd_ghost_node_map_ele_query( ghost->node_map, key, NULL, ghost->node_pool );
fd_ghost_node_map_ele_query( ghost->node_map, slot_hash, NULL, ghost->node_pool );

#if FD_GHOST_USE_HANDHOLDING
/* This indicates a programming error, because caller promises node is already in ghost. */
Expand All @@ -225,36 +225,34 @@ fd_ghost_replay_vote_upsert( fd_ghost_t * ghost,

/* Ignore votes for slots older than the current root. */

if( FD_UNLIKELY( key->slot < ghost->root.slot ) ) return;
if( FD_UNLIKELY( slot_hash->slot < ghost->root.slot ) ) return;

/* Query pubkey's previous vote. */

fd_ghost_vote_t * vote =
fd_ghost_vote_map_ele_query( ghost->vote_map, pubkey, NULL, ghost->vote_pool );
fd_ghost_vote_t * prev_vote =
fd_ghost_vote_map_ele_query( ghost->replay_vote_map, pubkey, NULL, ghost->vote_pool );

/* Subtract the vote stake from the old tree. */

if( FD_LIKELY( vote ) ) {
if( FD_LIKELY( prev_vote ) ) {

/* Return early if the vote hasn't changed. */

if( FD_SLOT_HASH_EQ( key, &vote->slot_hash ) ) return;
if( FD_SLOT_HASH_EQ( slot_hash, &prev_vote->slot_hash ) ) return;

/* TODO also keep track of node's stake changes to return early? */

fd_ghost_node_t * node =
fd_ghost_node_map_ele_query( ghost->node_map, &vote->slot_hash, NULL, ghost->node_pool );
fd_ghost_node_t * prev_node =
fd_ghost_node_map_ele_query( ghost->node_map, &prev_vote->slot_hash, NULL, ghost->node_pool );

#if FD_GHOST_USE_HANDHOLDING
/* This indicates a programming error, because if there is a vote that implies there must be a
* node. */
if( FD_UNLIKELY( !node ) ) FD_LOG_ERR( ( "missing ghost node that is in votes" ) );
if( FD_UNLIKELY( !prev_node ) ) FD_LOG_ERR( ( "missing ghost node that is in votes" ) );
#endif

node->stake -= vote->stake;
fd_ghost_node_t * ancestor = node;
prev_node->stake -= prev_vote->stake;
fd_ghost_node_t * ancestor = prev_node;
while( ancestor ) {
ancestor->weight -= vote->stake;
ancestor->weight -= prev_vote->stake;
ancestor = ancestor->parent;
}
} else {
Expand All @@ -269,17 +267,17 @@ fd_ghost_replay_vote_upsert( fd_ghost_t * ghost,
}
#endif

vote = fd_ghost_vote_pool_ele_acquire( ghost->vote_pool );
vote->pubkey = *pubkey;
vote->slot_hash = *key;
vote->stake = stake;
fd_ghost_vote_map_ele_insert( ghost->vote_map, vote, ghost->vote_pool );
prev_vote = fd_ghost_vote_pool_ele_acquire( ghost->vote_pool );
prev_vote->pubkey = *pubkey;
prev_vote->slot_hash = *slot_hash;
prev_vote->stake = stake;
fd_ghost_vote_map_ele_insert( ghost->replay_vote_map, prev_vote, ghost->vote_pool );
}

/* Add the vote stake to the fork ancestry beginning at key. */

vote->slot_hash = *key;
vote->stake = stake;
prev_vote->slot_hash = *slot_hash;
prev_vote->stake = stake;
node->stake += stake;
fd_ghost_node_t * ancestor = node;
while( ancestor ) {
Expand All @@ -306,14 +304,66 @@ fd_ghost_replay_vote_upsert( fd_ghost_t * ghost,
ancestor = ancestor->parent;
}
}

/* Remove the vote from gossip stake, if a gossip vote was previously counted. */

fd_ghost_vote_t * latest_gossip_vote =
fd_ghost_vote_map_ele_query( ghost->gossip_vote_map, pubkey, NULL, ghost->vote_pool );
if( FD_UNLIKELY( latest_gossip_vote && latest_gossip_vote->slot_hash.slot == slot_hash->slot ) ) {
node->gossip_stake -= latest_gossip_vote->stake;
}
}

void
fd_ghost_gossip_vote_upsert( FD_PARAM_UNUSED fd_ghost_t * ghost,
FD_PARAM_UNUSED fd_slot_hash_t const * slot_hash,
FD_PARAM_UNUSED fd_pubkey_t const * pubkey,
FD_PARAM_UNUSED ulong stake ) {
FD_LOG_ERR( ( "unimplemented" ) );
fd_ghost_gossip_vote_upsert( fd_ghost_t * ghost,
fd_slot_hash_t const * slot_hash,
fd_pubkey_t const * pubkey,
ulong stake ) {
fd_ghost_node_t * node =
fd_ghost_node_map_ele_query( ghost->node_map, slot_hash, NULL, ghost->node_pool );

#if FD_GHOST_USE_HANDHOLDING
/* This indicates a programming error, because caller promises node is already in ghost. */
if( FD_UNLIKELY( !node ) ) FD_LOG_ERR( ( "missing ghost node" ) );
#endif

ulong slot = slot_hash->slot;
fd_ghost_vote_t * latest_gossip_vote =
fd_ghost_vote_map_ele_query( ghost->gossip_vote_map, pubkey, NULL, ghost->vote_pool );
fd_ghost_vote_t * latest_replay_vote =
fd_ghost_vote_map_ele_query( ghost->replay_vote_map, pubkey, NULL, ghost->vote_pool );

/* Ignore votes for slots older than the current root. */

if( FD_UNLIKELY( slot < ghost->root.slot ) ) return;

/* Ignore duplicate or stale gossip votes. */

if( FD_UNLIKELY( latest_gossip_vote && slot <= latest_gossip_vote->slot_hash.slot ) ) return;

/* Ignore gossip votes less recent than replay votes. */

if( FD_UNLIKELY( latest_replay_vote && slot <= latest_replay_vote->slot_hash.slot ) ) return;

/* Gossip vote is more recent than replay vote, so count it towards the gossip stake */

node->gossip_stake += stake;

/* Insert the new pubkey's vote. */

#if FD_GHOST_USE_HANDHOLDING
/* This indicates a programming error, because we've exceeded the max # of node pubkeys that
* were statically allocated. */
if( FD_UNLIKELY( !fd_ghost_vote_pool_free( ghost->vote_pool ) ) ) {
FD_LOG_ERR( ( "vote pool full" ) ); /* OOM */
}
#endif

fd_ghost_vote_t * vote = fd_ghost_vote_pool_ele_acquire( ghost->vote_pool );
vote->pubkey = *pubkey;
vote->slot_hash = *slot_hash;
vote->stake = stake;
fd_ghost_vote_map_ele_insert( ghost->gossip_vote_map, vote, ghost->vote_pool );
}

static void
Expand Down
40 changes: 21 additions & 19 deletions src/choreo/ghost/fd_ghost.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ struct __attribute__((aligned(128UL))) fd_ghost {
fd_ghost_node_t * node_pool; /* memory pool of ghost nodes */
fd_ghost_node_map_t * node_map; /* map of slot_hash->fd_ghost_node_t */
fd_ghost_vote_t * vote_pool; /* memory pool of ghost votes */
fd_ghost_vote_map_t * vote_map; /* each node's latest vote. map of pubkey->fd_ghost_vote_t */
fd_ghost_vote_map_t * replay_vote_map; /* each node's latest replay vote. map of pubkey->fd_ghost_vote_t */
fd_ghost_vote_map_t * gossip_vote_map; /* same as replay vote, but from gossip */
};
typedef struct fd_ghost fd_ghost_t;
/* clang-format on */
Expand Down Expand Up @@ -172,39 +173,40 @@ fd_ghost_leaf_insert( fd_ghost_t * ghost,
fd_slot_hash_t const * key,
fd_slot_hash_t const * parent_key_opt );

/* fd_ghost_node_query finds the node corresponding to key. */
/* fd_ghost_node_query queries for slot_hash in ghost. */

fd_ghost_node_t *
fd_ghost_node_query( fd_ghost_t * ghost, fd_slot_hash_t const * key );
fd_ghost_node_query( fd_ghost_t * ghost, fd_slot_hash_t const * slot_hash );

/* fd_ghost_replay_vote_upsert updates or inserts pubkey's stake in ghost.
/* fd_ghost_replay_vote_upsert counts pubkey's stake towards slot_hash in ghost. Caller promises
slot_hash is in ghost.
The stake associated with pubkey is added to the ancestry chain beginning at slot hash
("insert"). If pubkey has previously voted, the previous vote's stake is removed from the
previous vote slot hash's ancestry chain ("update").
Stake is added to the ancestry chain beginning at slot_hash ("insert"). If pubkey has previously
voted, the previous vote's stake is removed from the previous vote slot hash's ancestry chain
("update").
TODO the implementation can be made more efficient by short-circuiting and doing fewer
traversals, but as it exists this is bounded to O(h), where h is the height of ghost.
If a gossip vote by pubkey has previously counted towards slot_hash, it is subtracted from
gossip_stake.
Note it is specific to the replay vote case that stake is propagated up the ancestry
chain.
*/
TODO the implementation can be made more efficient by short-circuiting and doing fewer
traversals. Asymptotically this has time complexity O(h), where h is the height of ghost. */

void
fd_ghost_replay_vote_upsert( fd_ghost_t * ghost,
fd_slot_hash_t const * key,
fd_slot_hash_t const * slot_hash,
fd_pubkey_t const * pubkey,
ulong stake );

/* fd_ghost_gossip_vote_upsert updates or inserts pubkey's stake in ghost.
Unlike fd_ghost_replay_vote_upsert, the stake associated with pubkey is not propagated. It is
only counted towards the individual slot hash voted for.
*/
/* fd_ghost_gossip_vote_upsert counts pubkey's stake towards slot_hash in ghost. Caller promises
slot_hash is in ghost.
.
Unlike fd_ghost_replay_vote_upsert, the stake associated with pubkey is not propagated up the
ancestry chain. It is only counted towards the individual slot hash voted for, and counted
towards a separately tracked gossip_stake sum. */

void
fd_ghost_gossip_vote_upsert( fd_ghost_t * ghost,
fd_slot_hash_t const * key,
fd_slot_hash_t const * slot_hash,
fd_pubkey_t const * pubkey,
ulong stake );

Expand Down
2 changes: 1 addition & 1 deletion src/choreo/ghost/test_ghost.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ main( int argc, char ** argv ) {
FD_TEST( ghost->node_pool );
FD_TEST( ghost->node_map );
FD_TEST( ghost->vote_pool );
FD_TEST( ghost->vote_map );
FD_TEST( ghost->replay_vote_map );

test_ghost_simple( ghost );

Expand Down
Loading

0 comments on commit 06819f6

Please sign in to comment.