From d72e36731c47f6ffd29ceea5738b2d23ff32acc0 Mon Sep 17 00:00:00 2001 From: Charles Li Date: Mon, 29 Apr 2024 16:38:58 +0000 Subject: [PATCH] feat(consensus): stub out tower module, tower_sync vote ixs This PR stubs out the fd_tower module which will implement various safe guards in the consensus protocol (threshold check, switch proof). It also stubs out the new tower_sync instruction in the vote program. Implementation in PR to follow. --- .gitignore | 7 +- doc/organization.txt | 5 +- src/choreo/bft/fd_bft.c | 89 ++++++++++++------- src/choreo/bft/fd_bft.h | 9 +- src/choreo/fd_choreo_base.h | 19 ++++ src/choreo/forks/fd_forks.h | 22 +---- src/choreo/ghost/fd_ghost.h | 24 ++--- src/choreo/tower/fd_tower.c | 33 +++++++ src/choreo/tower/fd_tower.h | 52 +++++++++++ src/disco/tvu/fd_tvu.c | 48 +++++----- .../runtime/context/fd_exec_slot_ctx.c | 4 +- .../runtime/context/fd_exec_slot_ctx.h | 19 ++-- src/flamenco/runtime/context/fd_tower_ctx.h | 22 ----- src/flamenco/runtime/fd_blockstore.c | 26 +++--- src/flamenco/runtime/fd_blockstore.h | 17 ++-- src/flamenco/runtime/fd_runtime.c | 5 +- .../runtime/program/fd_vote_program.c | 53 ++++++++++- 17 files changed, 301 insertions(+), 153 deletions(-) create mode 100644 src/choreo/tower/fd_tower.c create mode 100644 src/choreo/tower/fd_tower.h delete mode 100644 src/flamenco/runtime/context/fd_tower_ctx.h diff --git a/.gitignore b/.gitignore index 80139db6fd..01c1946cac 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,10 @@ target/ *.lcov *.profraw -# Clang +# Clangd +.cache .clangd +compile_commands.json # Rust **/Cargo.lock @@ -30,6 +32,9 @@ deps-bundle.tar.zst *.swp *.swo +# VSCode +.vscode + # Packet Captures *.pcap *.pcapng diff --git a/doc/organization.txt b/doc/organization.txt index 275877ae2d..990ffafdcc 100644 --- a/doc/organization.txt +++ b/doc/organization.txt @@ -34,10 +34,11 @@ Firedancer source tree │ │ needed for interoperability with the Solana ecosystem │ │ (hash functions, cryptographic algorithms) │ │ + | ├── choreo/ Consensus components (fork choice, voting) + │ │ │ ├── disco/ Tiles running on the tango messaging layer │ │ - │ ├── flamenco/ Major Solana runtime and consensus components - │ │ (bank, leader schedule, virtual machine, ...) + │ ├── flamenco/ Major Solana runtime components │ │ │ ├── funk/ Database optimized for storing Solana ledger and │ │ accounts diff --git a/src/choreo/bft/fd_bft.c b/src/choreo/bft/fd_bft.c index 2eee4cd314..bdf7b77f8c 100644 --- a/src/choreo/bft/fd_bft.c +++ b/src/choreo/bft/fd_bft.c @@ -6,6 +6,7 @@ #include "../fd_choreo_base.h" #include "../ghost/fd_ghost.h" +#include "../tower/fd_tower.h" #include "fd_bft.h" #pragma GCC diagnostic ignored "-Wformat" @@ -77,29 +78,45 @@ fd_bft_delete( void * bft ) { static void count_votes( fd_bft_t * bft, fd_fork_t * fork ) { - for( fd_tower_deque_iter_t iter = fd_tower_deque_iter_init( fork->slot_ctx.towers ); - !fd_tower_deque_iter_done( fork->slot_ctx.towers, iter ); - iter = fd_tower_deque_iter_next( fork->slot_ctx.towers, iter ) ) { - fd_tower_t * tower = fd_tower_deque_iter_ele( fork->slot_ctx.towers, iter ); + for( fd_latest_vote_deque_iter_t iter = + fd_latest_vote_deque_iter_init( fork->slot_ctx.latest_votes ); + !fd_latest_vote_deque_iter_done( fork->slot_ctx.latest_votes, iter ); + iter = fd_latest_vote_deque_iter_next( fork->slot_ctx.latest_votes, iter ) ) { + fd_latest_vote_t * latest_vote = + fd_latest_vote_deque_iter_ele( fork->slot_ctx.latest_votes, iter ); - ulong vote_slot = tower->slots[tower->cnt - 1]; + ulong latest_vote_slot = latest_vote->slot_hash.slot; /* Ignore votes for slots < snapshot_slot. */ - if( FD_UNLIKELY( vote_slot < bft->snapshot_slot ) ) continue; + if( FD_UNLIKELY( latest_vote_slot < bft->snapshot_slot ) ) continue; /* Look up _our_ bank hash for this vote slot. */ - fd_hash_t const * vote_hash = fd_blockstore_bank_hash_query( bft->blockstore, vote_slot ); - if( FD_UNLIKELY( !vote_hash ) ) { - FD_LOG_WARNING( ( "couldn't find bank hash for slot %lu", vote_slot ) ); + fd_hash_t const * bank_hash = + fd_blockstore_bank_hash_query( bft->blockstore, latest_vote_slot ); + + /* TODO we need to implement repair logic here */ + + if( FD_UNLIKELY( !bank_hash ) ) { + FD_LOG_WARNING( ( "couldn't find bank hash for slot %lu", latest_vote_slot ) ); + continue; + } + + /* TODO we need to implement eqv block logic here */ + + if( FD_UNLIKELY( memcmp( bank_hash, &latest_vote->slot_hash.hash, sizeof( fd_hash_t ) ) ) ) { + FD_LOG_WARNING( ( "possible equivocating block for %lu. ours: %32J vs. theirs: %32J", + latest_vote_slot, + bank_hash, + &latest_vote->slot_hash.hash ) ); continue; } - fd_slot_hash_t vote_key = { .slot = vote_slot, .hash = *vote_hash }; - /* Look up latest vote for this node (pubkey) and stake. */ + /* Look up the stake for this pubkey. */ - fd_pubkey_t * pubkey = &tower->vote_acc_addr; + fd_slot_hash_t slot_hash = { .slot = latest_vote_slot, .hash = *bank_hash }; + fd_pubkey_t * pubkey = &latest_vote->node_pubkey; fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( fork->slot_ctx.epoch_ctx ); fd_vote_accounts_pair_t_mapnode_t * root = epoch_bank->stakes.vote_accounts.vote_accounts_root; @@ -112,7 +129,7 @@ count_votes( fd_bft_t * bft, fd_fork_t * fork ) { /* If it's not in the epoch stakes map, look in the incremental-epoch stakes map. */ - if( !vote_node ) { + if( FD_UNLIKELY( !vote_node ) ) { pool = fork->slot_ctx.slot_bank.vote_account_keys.vote_accounts_pool; root = fork->slot_ctx.slot_bank.vote_account_keys.vote_accounts_root; fd_vote_accounts_pair_t_mapnode_t * vote_node = @@ -125,30 +142,32 @@ count_votes( fd_bft_t * bft, fd_fork_t * fork ) { /* Set the stake. */ - ulong stake = vote_node->elem.stake; - fd_ghost_node_t * node = fd_ghost_node_query( bft->ghost, &vote_key ); + ulong stake = vote_node->elem.stake; + fd_ghost_node_t * ghost_node = fd_ghost_node_query( bft->ghost, &slot_hash ); /* This slot hash must have been inserted, because ghost only processes replay votes. */ - if( FD_UNLIKELY( !node ) ) { - FD_LOG_ERR( ( "missing ghost key %lu %32J", vote_key.slot, vote_key.hash.hash ) ); + if( FD_UNLIKELY( !ghost_node ) ) { + FD_LOG_ERR( ( "missing ghost key %lu %32J", slot_hash.slot, slot_hash.hash.hash ) ); }; - fd_ghost_latest_vote_upsert( bft->ghost, &vote_key, pubkey, stake ); + fd_ghost_latest_vote_upsert( bft->ghost, &slot_hash, pubkey, stake ); - double pct = (double)node->stake / (double)bft->epoch_stake; - if( FD_UNLIKELY( !fork->eqv_safe && pct > FD_BFT_EQV_SAFE ) ) { - fork->eqv_safe = 1; + double pct = (double)ghost_node->stake / (double)bft->epoch_stake; + + if( FD_UNLIKELY( !ghost_node->eqv_safe && pct > FD_BFT_EQV_SAFE ) ) { + ghost_node->eqv_safe = 1; #if FD_BFT_USE_HANDHOLDING FD_LOG_NOTICE( - ( "[bft] eqv safe (%lf): (%lu, %32J)", pct, vote_key.slot, vote_key.hash.hash ) ); + ( "[bft] eqv safe (%lf): (%lu, %32J)", pct, slot_hash.slot, slot_hash.hash.hash ) ); #endif } - if( FD_UNLIKELY( !fork->opt_conf && pct > FD_BFT_OPT_CONF ) ) { - fork->opt_conf = 1; + + if( FD_UNLIKELY( !ghost_node->opt_conf && pct > FD_BFT_OPT_CONF ) ) { + ghost_node->opt_conf = 1; #if FD_BFT_USE_HANDHOLDING FD_LOG_NOTICE( - ( "[bft] opt conf (%lf): (%lu, %32J)", pct, vote_key.slot, vote_key.hash.hash ) ); + ( "[bft] opt conf (%lf): (%lu, %32J)", pct, slot_hash.slot, slot_hash.hash.hash ) ); #endif } } @@ -167,7 +186,7 @@ fd_bft_fork_update( fd_bft_t * bft, fd_fork_t * fork ) { /* Get the parent key. Every block must have a parent (except genesis or snapshot block). */ - ulong parent_slot = fd_blockstore_parent_slot_query(bft->blockstore, fork->slot); + ulong parent_slot = fd_blockstore_parent_slot_query( bft->blockstore, fork->slot ); /* Insert this fork into bft. */ @@ -185,7 +204,7 @@ fd_bft_fork_update( fd_bft_t * bft, fd_fork_t * fork ) { }; FD_LOG_NOTICE( ( "[ghost] insert slot: %lu hash: %32J parent: %lu parent_hash: %32J", curr_key.slot, - curr_key.hash.uc, + curr_key.hash.hash, parent_slot, parent_bank_hash->hash ) ); fd_ghost_leaf_insert( bft->ghost, &curr_key, &parent_key ); @@ -243,9 +262,13 @@ fd_bft_fork_choice( fd_bft_t * bft ) { fd_slot_hash_t key = { .slot = fork->slot, .hash = fork->slot_ctx.slot_bank.banks_hash }; fd_ghost_node_t * node = fd_ghost_node_query( ghost, &key ); - #if FD_BFT_USE_HANDHOLDING + /* do not pick forks with equivocating blocks. */ + + if( FD_UNLIKELY( node->eqv && !node->eqv_safe ) ) continue; + +#if FD_BFT_USE_HANDHOLDING - /* invariant: node must have been inserted by now */ + /* invariant: node must have been inserted by now. */ if( !node ) FD_LOG_ERR( ( "missing ghost node %lu", fork->slot ) ); #endif @@ -258,11 +281,13 @@ fd_bft_fork_choice( fd_bft_t * bft ) { if( heaviest_fork_key ) { double pct = (double)heaviest_fork_weight / (double)bft->epoch_stake; - FD_LOG_NOTICE( ( "[bft] voting for heaviest fork %lu %lu (%lf)", heaviest_fork_key->slot, heaviest_fork_weight, pct ) ); - // fd_ghost_print( ghost ); + FD_LOG_NOTICE( ( "[bft] voting for heaviest fork %lu %lu (%lf)", + heaviest_fork_key->slot, + heaviest_fork_weight, + pct ) ); } - return heaviest_fork_key; /* lifetime as long as it remains in forks->bft */ + return heaviest_fork_key; /* lifetime as long as it remains in the frontier */ } void diff --git a/src/choreo/bft/fd_bft.h b/src/choreo/bft/fd_bft.h index bd3ace9a6b..a80314ee69 100644 --- a/src/choreo/bft/fd_bft.h +++ b/src/choreo/bft/fd_bft.h @@ -8,6 +8,7 @@ #include "../fd_choreo_base.h" #include "../forks/fd_forks.h" #include "../ghost/fd_ghost.h" +#include "../tower/fd_tower.h" /* FD_BFT_USE_HANDHOLDING: Define this to non-zero at compile time to turn on additional runtime checks and logging. */ @@ -22,8 +23,9 @@ /* 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 */ + ulong snapshot_slot; + ulong epoch_stake; /* total amount of stake in the current epoch */ + fd_tower_t tower; /* our local vote tower */ /* external joins */ @@ -91,4 +93,7 @@ fd_bft_fork_choice( fd_bft_t * bft ); void fd_bft_epoch_stake_update( fd_bft_t * bft, fd_exec_epoch_ctx_t * epoch_ctx ); +void +fd_bft_tower_threshold_check( fd_bft_t * bft ); + #endif /* HEADER_fd_src_choreo_bft_fd_bft_h */ diff --git a/src/choreo/fd_choreo_base.h b/src/choreo/fd_choreo_base.h index 18d63ea528..b27feb3793 100644 --- a/src/choreo/fd_choreo_base.h +++ b/src/choreo/fd_choreo_base.h @@ -1,10 +1,29 @@ #ifndef HEADER_fd_src_choreo_fd_choreo_base_h #define HEADER_fd_src_choreo_fd_choreo_base_h +/* Choreo is the consensus library. + + - bft: wires it all together. + - eqv: equivocation (also called "duplicate block") handling. + - forks: data structures and associated functions to manage "forks", ie. competing views in the + canonical state of the blockchain. + - ghost: fork choice rule, ie. which fork is the best one that I should pick. + - tower: TowerBFT rules for reaching consensus by "finalizing" blocks, ie. you + can no longer rollback a block or switch to a different fork. + + Includes threshold check and switch proof. + + Note this is the TowerBFT implementation on the local ("self") side. The Vote Program is the + TowerBFT implementation for the cluster ("others") side. In other words, the local validator + runs through these rules before submitting the vote. The other validators in the cluster then + process the submitted vote using the Vote Program. */ + #include "../flamenco/fd_flamenco_base.h" #include "../flamenco/types/fd_types.h" /* clang-format off */ +#define FD_LG_NODE_MAX (16UL) /* the maximum number of nodes (unique pubkeys) consensus data structures will support */ + #define FD_SLOT_HASH_CMP(a,b) (fd_int_if(((a)->slot)<((b)->slot),-1,fd_int_if(((a)->slot)>((b)->slot),1),memcmp((a),(b),sizeof(fd_slot_hash_t)))) #define FD_SLOT_HASH_EQ(a,b) ((((a)->slot)==((b)->slot)) & !(memcmp(((a)->hash.uc),((b)->hash.uc),sizeof(fd_hash_t)))) /* clang-format on */ diff --git a/src/choreo/forks/fd_forks.h b/src/choreo/forks/fd_forks.h index dbd7ef1ead..008f9bebcd 100644 --- a/src/choreo/forks/fd_forks.h +++ b/src/choreo/forks/fd_forks.h @@ -6,25 +6,11 @@ #include "../../flamenco/runtime/fd_blockstore.h" #include "../fd_choreo_base.h" -/* FD_FORKS_USE_HANDHOLDING: Define this to non-zero at compile time - to turn on additional runtime checks and logging. */ - -#ifndef FD_FORKS_USE_HANDHOLDING -#define FD_FORKS_USE_HANDHOLDING 1 -#endif - -#define FD_FORKS_EQV_SAFE ( 0.52 ) -#define FD_FORKS_OPT_CONF ( 2.0 / 3.0 ) - struct fd_fork { - ulong slot; /* head of the fork, frontier key */ - ulong next; /* reserved for use by fd_pool and fd_map_chain */ - - int eqv_safe; /* equivocation safe */ - int opt_conf; /* optimistically confirmed */ - - fd_exec_slot_ctx_t slot_ctx; - fd_block_t * head; /* the block representing the head of the fork */ + ulong slot; /* head of the fork, frontier key */ + ulong next; /* reserved for use by fd_pool and fd_map_chain */ + fd_block_t * head; /* the block representing the head of the fork */ + fd_exec_slot_ctx_t slot_ctx; /* the bank representing the head of the fork */ }; typedef struct fd_fork fd_fork_t; diff --git a/src/choreo/ghost/fd_ghost.h b/src/choreo/ghost/fd_ghost.h index 2c6aa9818d..88ca45b1ad 100644 --- a/src/choreo/ghost/fd_ghost.h +++ b/src/choreo/ghost/fd_ghost.h @@ -32,18 +32,22 @@ /* clang-format off */ typedef struct fd_ghost_node fd_ghost_node_t; struct __attribute__((aligned(128UL))) fd_ghost_node { - fd_slot_hash_t key; /* (slot, bank_hash) to index ghost */ - ulong next; /* reserved for internal use by fd_pool and fd_map_chain */ - ulong weight; /* sum of stake for the subtree rooted at this slot hash */ - ulong stake; /* stake amount for only this slot hash */ - fd_ghost_node_t * head; /* the head of the fork i.e. leaf of the highest-weight subtree */ - fd_ghost_node_t * parent; /* parent slot hash */ - fd_ghost_node_t * child; /* pointer to the left-most child */ - fd_ghost_node_t * sibling; /* pointer to next sibling */ + fd_slot_hash_t key; /* (slot, bank_hash) to index ghost */ + ulong next; /* reserved for internal use by fd_pool and fd_map_chain */ + ulong weight; /* sum of stake for the subtree rooted at this slot hash */ + ulong stake; /* stake amount for only this slot hash */ + int eqv; /* flag for equivocation (multiple blocks) in this slot */ + int eqv_safe; /* flag for equivocation safety (ie. 52% of stake has voted for this slot hash) */ + int opt_conf; /* flag for optimistic confirmation (ie. 2/3 of stake has voted for this slot hash ) */ + fd_ghost_node_t * head; /* the head of the fork i.e. leaf of the highest-weight subtree */ + fd_ghost_node_t * parent; /* parent slot hash */ + fd_ghost_node_t * child; /* pointer to the left-most child */ + fd_ghost_node_t * sibling; /* pointer to next sibling */ }; -/* clang-format on */ -/* clang-format off */ +#define FD_GHOST_EQV_SAFE ( 0.52 ) +#define FD_GHOST_OPT_CONF ( 2.0 / 3.0 ) + /* fork a's weight > fork b's weight, with lower slot # as tie-break */ #define FD_GHOST_NODE_MAX(a,b) (fd_ptr_if(fd_int_if(a->weight==b->weight, a->key.slotkey.slot, a->weight>b->weight),a,b)) #define FD_GHOST_NODE_EQ(a,b) (FD_SLOT_HASH_EQ(&a->key,&b->key)) diff --git a/src/choreo/tower/fd_tower.c b/src/choreo/tower/fd_tower.c new file mode 100644 index 0000000000..4b715ad922 --- /dev/null +++ b/src/choreo/tower/fd_tower.c @@ -0,0 +1,33 @@ +#include "fd_tower.h" + +int +fd_tower_threshold_check( fd_tower_t * tower, + ulong total_stake, + ulong threshold_depth, + float threshold_pct ) { + if( FD_UNLIKELY( tower->slots_cnt < threshold_depth ) ) return 1; + ulong slot = tower->slots[tower->slots_cnt - threshold_depth]; + fd_hash_t const * hash = fd_blockstore_bank_hash_query( tower->blockstore, slot ); + fd_slot_hash_t slot_hash = { .slot = slot, .hash = *hash }; + + fd_ghost_node_t const * ghost_node = fd_ghost_node_query( tower->ghost, &slot_hash ); + +#if FD_TOWER_USE_HANDHOLDING + + /* This shouldn't happen because slot hashes are inserted into the ghost upon execution. Indicates + * a likely programming error. */ + + if( FD_UNLIKELY( ghost_node == NULL ) ) { + FD_LOG_ERR( ( "invariant violation: slot %lu, hash: %32J not found in ghost", slot, hash->hash ) ); + } + +#endif + + float pct = (float)ghost_node->weight / (float)total_stake; + return pct > threshold_pct; +} + +int +fd_tower_switch_proof_construct( fd_tower_t * tower ) { + FD_LOG_ERR(("unimplemented")); +} diff --git a/src/choreo/tower/fd_tower.h b/src/choreo/tower/fd_tower.h new file mode 100644 index 0000000000..5576d90e11 --- /dev/null +++ b/src/choreo/tower/fd_tower.h @@ -0,0 +1,52 @@ +#ifndef HEADER_fd_src_choreo_tower_fd_tower_h +#define HEADER_fd_src_choreo_tower_fd_tower_h + +#include "../../flamenco/runtime/fd_blockstore.h" +#include "../fd_choreo_base.h" +#include "../forks/fd_forks.h" +#include "../ghost/fd_ghost.h" + +/* FD_TOWER_USE_HANDHOLDING: Define this to non-zero at compile time + to turn on additional runtime checks and logging. */ + +#ifndef FD_TOWER_USE_HANDHOLDING +#define FD_TOWER_USE_HANDHOLDING 1 +#endif + +#define FD_TOWER_THRESHOLD_CHECK_DEPTH ( 8 ) +#define FD_TOWER_THRESHOLD_CHECK_PCT ( 2.0 / 3.0 ) +#define FD_TOWER_SHALLOW_THRESHOLD_CHECK_DEPTH ( 4 ) +#define FD_TOWER_SHALLOW_THRESHOLD_CHECK_PCT ( 0.38 ) + +/* Maintain our local vote tower */ + +struct fd_tower { + ulong slots[32]; + ulong slots_cnt; + fd_hash_t root; + + fd_blockstore_t * blockstore; + fd_ghost_t * ghost; +}; +typedef struct fd_tower fd_tower_t; + +int +fd_tower_threshold_check( fd_tower_t * tower, + ulong total_stake, + ulong threshold_depth, + float threshold_pct ); + +/* Attempt to construct a "switch proof", ie. demonstrate that at least +FD_TOWER_SWITCH_PROOF_THRESHOLD_PCT is locked out from voting for our current fork. + +A validator is time-locked out from voting for other forks on a given slot n for 2^k slots, where k +is the confirmation count. Once locked out, a validator can only vote for descendants until that +lockout expires. + +A switch proof is an additional constraint validators must satisfy to be able to switch forks. It is used to safeguard optimistic confirmation + +*/ +fd_hash_t const * +fd_tower_switch_proof_construct( fd_tower_t * tower ); + +#endif /* HEADER_fd_src_choreo_tower_fd_tower_h */ diff --git a/src/disco/tvu/fd_tvu.c b/src/disco/tvu/fd_tvu.c index b5ef474433..367ba74a43 100644 --- a/src/disco/tvu/fd_tvu.c +++ b/src/disco/tvu/fd_tvu.c @@ -341,11 +341,11 @@ struct fd_gossip_thread_args { static int fd_gossip_thread( int argc, char ** argv ); static fd_exec_slot_ctx_t * -fd_tvu_late_incr_snap( fd_runtime_ctx_t * runtime_ctx, - fd_runtime_args_t * runtime_args, - fd_replay_t * replay, - ulong snapshot_slot, - fd_tower_t * towers ); +fd_tvu_late_incr_snap( fd_runtime_ctx_t * runtime_ctx, + fd_runtime_args_t * runtime_args, + fd_replay_t * replay, + ulong snapshot_slot, + fd_latest_vote_t * latest_votes ); int fd_tvu_main( fd_runtime_ctx_t * runtime_ctx, @@ -442,7 +442,7 @@ fd_tvu_main( fd_runtime_ctx_t * runtime_ctx, nanosleep(&ts, NULL); //if( fd_tile_shutdown_flag ) goto shutdown; } - slot_ctx = fd_tvu_late_incr_snap( runtime_ctx, runtime_args, replay, slot_ctx->slot_bank.slot, slot_ctx->towers ); + slot_ctx = fd_tvu_late_incr_snap( runtime_ctx, runtime_args, replay, slot_ctx->slot_bank.slot, slot_ctx->latest_votes ); runtime_ctx->need_incr_snap = 0; } @@ -974,11 +974,11 @@ void snapshot_setup( char const * snapshot, } void -snapshot_insert( fd_fork_t * fork, - ulong snapshot_slot, - fd_blockstore_t * blockstore, - fd_replay_t * replay, - fd_tower_t * towers ) { +snapshot_insert( fd_fork_t * fork, + ulong snapshot_slot, + fd_blockstore_t * blockstore, + fd_replay_t * replay, + fd_latest_vote_t * latest_votes ) { /* Add snapshot slot to blockstore.*/ @@ -989,9 +989,9 @@ snapshot_insert( fd_fork_t * fork, fork->slot = snapshot_slot; fd_fork_frontier_ele_insert( replay->forks->frontier, fork, replay->forks->pool ); - /* Set the towers pointer to passed-in towers mem. */ + /* Set the latest_votes pointer to passed-in latest_votes mem. */ - fork->slot_ctx.towers = towers; + fork->slot_ctx.latest_votes = latest_votes; /* Add snapshot slot to ghost. */ @@ -1017,7 +1017,7 @@ fd_tvu_late_incr_snap( fd_runtime_ctx_t * runtime_ctx, fd_runtime_args_t * runtime_args, fd_replay_t * replay, ulong snapshot_slot, - fd_tower_t * towers ) { + fd_latest_vote_t * latest_votes ) { (void)runtime_ctx; fd_fork_t * fork = fd_fork_pool_ele_acquire( replay->forks->pool ); @@ -1041,7 +1041,7 @@ fd_tvu_late_incr_snap( fd_runtime_ctx_t * runtime_ctx, slot_ctx->slot_bank.collected_fees = 0; slot_ctx->slot_bank.collected_rent = 0; - snapshot_insert( fork, snapshot_slot, replay->blockstore, replay, towers); + snapshot_insert( fork, snapshot_slot, replay->blockstore, replay, latest_votes); return slot_ctx; } @@ -1204,14 +1204,14 @@ fd_tvu_main_setup( fd_runtime_ctx_t * runtime_ctx, fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 1024UL, 1 << 16, 42UL ) ); /***********************************************************************/ - /* towers */ + /* latest_votes */ /***********************************************************************/ - void * towers_mem = - fd_wksp_alloc_laddr( wksp, fd_tower_deque_align(), fd_tower_deque_footprint(), 42UL ); - fd_tower_t * towers = - fd_tower_deque_join( fd_tower_deque_new( towers_mem ) ); - FD_TEST( towers ); + void * latest_votes_mem = + fd_wksp_alloc_laddr( wksp, fd_latest_vote_deque_align(), fd_latest_vote_deque_footprint(), 42UL ); + fd_latest_vote_t * latest_votes = + fd_latest_vote_deque_join( fd_latest_vote_deque_new( latest_votes_mem ) ); + FD_TEST( latest_votes ); /***********************************************************************/ /* bft */ @@ -1344,13 +1344,13 @@ fd_tvu_main_setup( fd_runtime_ctx_t * runtime_ctx, /* bootstrap replay with the snapshot slot */ - slot_ctx_setup_out.exec_slot_ctx->towers = towers; + slot_ctx_setup_out.exec_slot_ctx->latest_votes = latest_votes; if( !runtime_ctx->need_incr_snap ) { snapshot_insert( slot_ctx_setup_out.fork, slot_ctx_setup_out.exec_slot_ctx->slot_bank.slot, blockstore_setup_out.blockstore, replay_setup_out.replay, - towers ); + latest_votes ); } /* TODO @yunzhang open files, set the replay pointers, etc. you need here*/ @@ -1431,7 +1431,7 @@ fd_tvu_parse_args( fd_runtime_args_t * args, int argc, char ** argv ) { fd_env_strip_cmdline_cstr( &argc, &argv, "--check_hash", NULL, "false" ); args->capture_fpath = fd_env_strip_cmdline_cstr( &argc, &argv, "--capture", NULL, NULL ); /* Disabling capture_txns speeds up runtime and makes solcap captures significantly smaller */ - args->capture_txns = fd_env_strip_cmdline_cstr( &argc, &argv, "--capture-txns", NULL, "true" ); + args->capture_txns = fd_env_strip_cmdline_cstr( &argc, &argv, "--capture-txns", NULL, "false" ); args->trace_fpath = fd_env_strip_cmdline_cstr( &argc, &argv, "--trace", NULL, NULL ); /* TODO @yunzhang: I added this to get the shred_cap file path, * but shred_cap is now NULL despite there is such an entry in the toml config */ diff --git a/src/flamenco/runtime/context/fd_exec_slot_ctx.c b/src/flamenco/runtime/context/fd_exec_slot_ctx.c index e46d8288bf..c2d211c256 100644 --- a/src/flamenco/runtime/context/fd_exec_slot_ctx.c +++ b/src/flamenco/runtime/context/fd_exec_slot_ctx.c @@ -23,9 +23,9 @@ fd_exec_slot_ctx_new( void * mem, fd_exec_slot_ctx_t * self = (fd_exec_slot_ctx_t *) mem; self->valloc = valloc; - self->towers = NULL; - fd_slot_bank_new(&self->slot_bank); + + self->latest_votes = NULL; self->sysvar_cache = fd_sysvar_cache_new( fd_valloc_malloc( valloc, fd_sysvar_cache_align(), fd_sysvar_cache_footprint() ), valloc ); self->account_compute_table = fd_account_compute_table_join( fd_account_compute_table_new( fd_valloc_malloc( valloc, fd_account_compute_table_align(), fd_account_compute_table_footprint( 10000 ) ), 10000, 0 ) ); diff --git a/src/flamenco/runtime/context/fd_exec_slot_ctx.h b/src/flamenco/runtime/context/fd_exec_slot_ctx.h index f192d95051..f129747e58 100644 --- a/src/flamenco/runtime/context/fd_exec_slot_ctx.h +++ b/src/flamenco/runtime/context/fd_exec_slot_ctx.h @@ -11,20 +11,17 @@ #include "../sysvar/fd_sysvar_cache_old.h" #include "../../types/fd_types.h" -/* fd_tower is represents a given pubkey's vote tower. */ +/* fd_latest_vote_t records the latest voted slot hash by a given node. */ -struct fd_tower { +struct fd_latest_vote { fd_pubkey_t node_pubkey; - fd_pubkey_t vote_acc_addr; - fd_option_slot_t root; - ulong slots[32]; - ulong cnt; + fd_slot_hash_t slot_hash; }; -typedef struct fd_tower fd_tower_t; +typedef struct fd_latest_vote fd_latest_vote_t; -#define DEQUE_NAME fd_tower_deque -#define DEQUE_T fd_tower_t -#define DEQUE_MAX 10000UL +#define DEQUE_NAME fd_latest_vote_deque +#define DEQUE_T fd_latest_vote_t +#define DEQUE_MAX (1UL << 16) #include "../../../util/tmpl/fd_deque.c" struct fd_account_compute_elem { @@ -84,7 +81,7 @@ struct __attribute__((aligned(8UL))) fd_exec_slot_ctx { fd_hash_t account_delta_hash; fd_hash_t prev_banks_hash; - fd_tower_t * towers; + fd_latest_vote_t * latest_votes; fd_sysvar_cache_t * sysvar_cache; fd_account_compute_elem_t * account_compute_table; }; diff --git a/src/flamenco/runtime/context/fd_tower_ctx.h b/src/flamenco/runtime/context/fd_tower_ctx.h deleted file mode 100644 index 3fa63294fc..0000000000 --- a/src/flamenco/runtime/context/fd_tower_ctx.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef HEADER_fd_src_flamenco_runtime_context_fd_tower_ctx_h -#define HEADER_fd_src_flamenco_runtime_context_fd_tower_ctx_h - -#include "../../../funk/fd_funk_txn.h" - -struct fd_tower_entry { - fd_funk_txn_t * txn; - ulong slot; -}; - -typedef struct fd_tower_entry fd_tower_entry_t; - -struct fd_tower_ctx { - fd_funk_txn_t * blockage; - fd_tower_entry_t funk_txn_tower[32]; - ushort funk_txn_index; - uchar constipate; -}; - -typedef struct fd_tower_ctx fd_tower_ctx_t; - -#endif /* HEADER_fd_src_flamenco_runtime_context_fd_tower_ctx_h */ diff --git a/src/flamenco/runtime/fd_blockstore.c b/src/flamenco/runtime/fd_blockstore.c index c8db637a1c..b6e0925a99 100644 --- a/src/flamenco/runtime/fd_blockstore.c +++ b/src/flamenco/runtime/fd_blockstore.c @@ -478,7 +478,7 @@ fd_blockstore_slot_remove( fd_blockstore_t * blockstore, ulong slot ) { /* Remove all the unassembled shreds for a slot */ int -fd_blockstore_shreds_remove( fd_blockstore_t * blockstore, ulong slot ) { +fd_blockstore_buffered_shreds_remove( fd_blockstore_t * blockstore, ulong slot ) { fd_wksp_t * wksp = fd_blockstore_wksp( blockstore ); fd_blockstore_slot_map_t * slot_map = fd_wksp_laddr_fast( wksp, blockstore->slot_map_gaddr ); fd_blockstore_slot_map_t * slot_entry = fd_blockstore_slot_map_query( slot_map, slot, NULL ); @@ -822,7 +822,6 @@ fd_blockstore_block_query( fd_blockstore_t * blockstore, ulong slot ) { return &query->block; } -/* Get the final poh hash for a given slot */ fd_hash_t const * fd_blockstore_block_hash_query( fd_blockstore_t * blockstore, ulong slot ) { fd_blockstore_slot_map_t * query = @@ -837,7 +836,6 @@ fd_blockstore_block_hash_query( fd_blockstore_t * blockstore, ulong slot ) { return (fd_hash_t *)fd_type_pun( last_micro->hash ); } -/* Get the bank hash for a given slot */ fd_hash_t const * fd_blockstore_bank_hash_query( fd_blockstore_t * blockstore, ulong slot ) { fd_blockstore_slot_map_t * query = @@ -857,7 +855,6 @@ fd_blockstore_slot_meta_query( fd_blockstore_t * blockstore, ulong slot ) { return &query->slot_meta; } -/* Return the slot of the parent block */ ulong fd_blockstore_parent_slot_query( fd_blockstore_t * blockstore, ulong slot ) { fd_blockstore_slot_map_t * query = @@ -866,7 +863,6 @@ fd_blockstore_parent_slot_query( fd_blockstore_t * blockstore, ulong slot ) { return query->slot_meta.parent_slot; } -/* Return the slot array and length of the children blocks */ int fd_blockstore_next_slot_query( fd_blockstore_t * blockstore, ulong slot , ulong ** next_slot_out, ulong * next_slot_len_out) { fd_blockstore_slot_map_t * query = @@ -877,7 +873,6 @@ fd_blockstore_next_slot_query( fd_blockstore_t * blockstore, ulong slot , ulong return FD_BLOCKSTORE_OK; } -/* Returns the transaction data for the given signature */ fd_blockstore_txn_map_t * fd_blockstore_txn_query( fd_blockstore_t * blockstore, uchar const sig[FD_ED25519_SIG_SZ] ) { fd_blockstore_txn_key_t key; @@ -888,9 +883,8 @@ fd_blockstore_txn_query( fd_blockstore_t * blockstore, uchar const sig[FD_ED2551 NULL ); } -/* Update the height for a block */ void -fd_blockstore_block_height_update( fd_blockstore_t * blockstore, ulong slot, ulong block_height ) { +fd_blockstore_block_height_set( fd_blockstore_t * blockstore, ulong slot, ulong block_height ) { fd_block_t * query = fd_blockstore_block_query( blockstore, slot ); if( query ) query->height = block_height; } @@ -992,19 +986,21 @@ fd_blockstore_log_mem_usage( fd_blockstore_t * blockstore ) { } void -fd_blockstore_snapshot_insert( fd_blockstore_t * blockstore, fd_slot_bank_t const * slot_bank ) { - fd_blockstore_slot_map_t * slot_entry = - fd_blockstore_slot_map_insert( fd_blockstore_slot_map( blockstore ), slot_bank->slot ); +fd_blockstore_snapshot_insert( fd_blockstore_t * blockstore, fd_slot_bank_t const * snapshot_slot_bank ) { + blockstore->min = blockstore->max = blockstore->smr = snapshot_slot_bank->slot; + fd_blockstore_slot_map_t * slot_entry = + fd_blockstore_slot_map_insert( fd_blockstore_slot_map( blockstore ), snapshot_slot_bank->slot ); + /* fake the snapshot slot meta */ fd_slot_meta_t * slot_meta = &slot_entry->slot_meta; - slot_meta->slot = slot_bank->slot; + slot_meta->slot = snapshot_slot_bank->slot; slot_meta->consumed = 0; slot_meta->received = 0; slot_meta->first_shred_timestamp = 0; slot_meta->last_index = 0; - slot_meta->parent_slot = slot_bank->prev_slot; + slot_meta->parent_slot = snapshot_slot_bank->prev_slot; slot_meta->next_slot_len = 0; slot_meta->next_slot = fd_alloc_malloc( fd_blockstore_alloc( blockstore ), sizeof( ulong ), FD_BLOCKSTORE_NEXT_SLOT_MAX ); @@ -1014,8 +1010,8 @@ fd_blockstore_snapshot_insert( fd_blockstore_t * blockstore, fd_slot_bank_t cons fd_block_t * block = &slot_entry->block; block->data_gaddr = ULONG_MAX; - block->height = slot_bank->block_height; - block->bank_hash = slot_bank->banks_hash; + block->height = snapshot_slot_bank->block_height; + block->bank_hash = snapshot_slot_bank->banks_hash; uchar flags[8] = { FD_BLOCK_FLAG_PROCESSED, FD_BLOCK_FLAG_EQV_SAFE, diff --git a/src/flamenco/runtime/fd_blockstore.h b/src/flamenco/runtime/fd_blockstore.h index 5b338bf6c5..e15485f13e 100644 --- a/src/flamenco/runtime/fd_blockstore.h +++ b/src/flamenco/runtime/fd_blockstore.h @@ -243,6 +243,7 @@ struct __attribute__((aligned(FD_BLOCKSTORE_ALIGN))) fd_blockstore_private { ulong root; /* the current root slot */ ulong min; /* the min slot still in the blockstore */ ulong max; /* the max slot in the blockstore */ + ulong smr; /* the super-majority root */ /* Internal data structures */ @@ -253,7 +254,7 @@ struct __attribute__((aligned(FD_BLOCKSTORE_ALIGN))) fd_blockstore_private { int lg_slot_max; ulong slot_max; /* maximum block history */ ulong slot_max_with_slop; /* maximum block history with some padding */ - ulong slot_map_gaddr; /* map of slot->(slot_meta, deshredder, block) */ + ulong slot_map_gaddr; /* map of slot->(slot_meta, block) */ int lg_txn_max; ulong txn_map_gaddr; @@ -454,11 +455,11 @@ fd_blockstore_bank_hash_query( fd_blockstore_t * blockstore, ulong slot ); fd_slot_meta_t * fd_blockstore_slot_meta_query( fd_blockstore_t * blockstore, ulong slot ); -/* Query the parent slot of slot */ +/* Query the parent slot of slot. */ ulong fd_blockstore_parent_slot_query( fd_blockstore_t * blockstore, ulong slot ); -/* Query the children slots of slot */ +/* Query the child slots of slot. `next_slot_out` must be at least */ int fd_blockstore_next_slot_query( fd_blockstore_t * blockstore, ulong slot , ulong ** next_slot_out, ulong * next_slot_len_out); @@ -477,9 +478,9 @@ fd_blockstore_txn_query( fd_blockstore_t * blockstore, uchar const sig[FD_ED2551 int fd_blockstore_slot_remove( fd_blockstore_t * blockstore, ulong slot ); -/* Remove all the tmp shreds for slot. */ +/* Remove all the unassembled shreds for a slot */ int -fd_blockstore_tmp_shreds_remove( fd_blockstore_t * blockstore, ulong slot ); +fd_blockstore_buffered_shreds_remove( fd_blockstore_t * blockstore, ulong slot ); /* Remove all slots less than min_slots from blockstore by removing them from all relevant internal structures. Used to maintain @@ -491,15 +492,15 @@ fd_blockstore_slot_history_remove( fd_blockstore_t * blockstore, ulong min_slot int fd_blockstore_clear( fd_blockstore_t * blockstore ); -/* Determine if a slot is ancient and we should ignore shreds */ +/* Determine if a slot is ancient and we should ignore shreds. */ static inline int fd_blockstore_is_slot_ancient( fd_blockstore_t * blockstore, ulong slot ) { return ( slot + blockstore->slot_max <= blockstore->max ); } -/* Set the height for a block */ +/* Set the block height. */ void -fd_blockstore_block_height_update( fd_blockstore_t * blockstore, ulong slot, ulong block_height ); +fd_blockstore_block_height_set( fd_blockstore_t * blockstore, ulong slot, ulong block_height ); /* Acquire a read lock */ static inline void diff --git a/src/flamenco/runtime/fd_runtime.c b/src/flamenco/runtime/fd_runtime.c index 5088f2fccc..74258f6e94 100644 --- a/src/flamenco/runtime/fd_runtime.c +++ b/src/flamenco/runtime/fd_runtime.c @@ -3148,9 +3148,8 @@ int fd_runtime_save_slot_bank(fd_exec_slot_ctx_t *slot_ctx) // Update blockstore if ( slot_ctx->blockstore != NULL ) { - fd_blockstore_block_height_update( slot_ctx->blockstore, - slot_ctx->slot_bank.slot, - slot_ctx->slot_bank.block_height ); + fd_blockstore_block_height_set( + slot_ctx->blockstore, slot_ctx->slot_bank.slot, slot_ctx->slot_bank.block_height ); } else { FD_LOG_WARNING(( "NULL blockstore in slot_ctx" )); } diff --git a/src/flamenco/runtime/program/fd_vote_program.c b/src/flamenco/runtime/program/fd_vote_program.c index 5c899ddbae..941ba0eeb0 100644 --- a/src/flamenco/runtime/program/fd_vote_program.c +++ b/src/flamenco/runtime/program/fd_vote_program.c @@ -1261,7 +1261,7 @@ process_new_vote_state( fd_vote_state_t * vote_state, fd_landed_vote_t * landed_vote = deq_fd_landed_vote_t_iter_ele( new_state, iter ); deq_fd_landed_vote_t_push_tail( vote_state->votes, *landed_vote ); } - return 0; + return FD_EXECUTOR_INSTR_SUCCESS; } // https://github.com/firedancer-io/solana/blob/da470eef4652b3b22598a1f379cacfe82bd5928d/programs/vote/src/vote_state/mod.rs#L776 @@ -1701,6 +1701,7 @@ do_process_vote_state_update( fd_vote_state_t * vote_state, deq_fd_landed_vote_t_push_tail( landed_votes, ( fd_landed_vote_t ){ .latency = 0, .lockout = *lockout } ); } + return process_new_vote_state( vote_state, landed_votes, vote_state_update->has_root, @@ -1729,7 +1730,18 @@ process_vote_state_update( ulong vote_acct_idx, &vote_state, slot_hashes, clock->epoch, clock->slot, vote_state_update, ctx ); if( FD_UNLIKELY( rc ) ) return rc; - return set_vote_account_state( vote_acct_idx, vote_account, &vote_state, ctx ); + rc = set_vote_account_state( vote_acct_idx, vote_account, &vote_state, ctx ); + + if( FD_LIKELY( rc == FD_EXECUTOR_INSTR_SUCCESS ) ) { + fd_landed_vote_t * latest_landed_vote = deq_fd_landed_vote_t_peek_tail( vote_state.votes ); + fd_latest_vote_t latest_vote = { + .node_pubkey = *vote_account->pubkey, + .slot_hash = { .slot = latest_landed_vote->lockout.slot, .hash = vote_state_update->hash } + }; + fd_latest_vote_deque_push_tail( ctx->slot_ctx->latest_votes, latest_vote ); + } + + return rc; } /**********************************************************************/ @@ -2062,7 +2074,7 @@ fd_vote_program_execute( fd_exec_instr_ctx_t ctx ) { if( decode_result != FD_BINCODE_SUCCESS || (ulong)ctx.instr->data + 1232UL < (ulong)decode.data ) return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; - + /* PLEASE PRESERVE SWITCH-CASE ORDERING TO MIRROR LABS IMPL: * https://github.com/firedancer-io/solana/blob/da470eef4652b3b22598a1f379cacfe82bd5928d/programs/vote/src/vote_processor.rs#L73 */ @@ -2347,6 +2359,17 @@ fd_vote_program_execute( fd_exec_instr_ctx_t ctx ) { return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR; rc = process_vote_state_update( 0, me, slot_hashes, clock, vote_state_update, signers, &ctx ); + + if( FD_LIKELY( rc == FD_EXECUTOR_INSTR_SUCCESS ) ) { + fd_vote_lockout_t * latest_vote_lockout = + deq_fd_vote_lockout_t_peek_tail( vote_state_update->lockouts ); + fd_latest_vote_t latest_vote = { + .node_pubkey = *me->pubkey, + .slot_hash = { .slot = latest_vote_lockout->slot, .hash = vote_state_update->hash } + }; + fd_latest_vote_deque_push_tail( ctx.slot_ctx->latest_votes, latest_vote ); + } + } else { // https://github.com/firedancer-io/solana/blob/da470eef4652b3b22598a1f379cacfe82bd5928d/programs/vote/src/vote_processor.rs#L198-L200 rc = FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; @@ -2415,6 +2438,30 @@ fd_vote_program_execute( fd_exec_instr_ctx_t ctx ) { } break; } + /* TowerSync + * + * Instruction: + * https://github.com/anza-xyz/agave/blob/master/sdk/program/src/vote/instruction.rs#L151-L157 + * + * Processor: + * https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_processor.rs#L196-L215 + */ + case fd_vote_instruction_enum_tower_sync:; + /* clang-format off */ + __attribute__((fallthrough)); + /* clang-format on */ + + /* TowerSyncSwitch + * + * Instruction: + * https://github.com/anza-xyz/agave/blob/master/sdk/program/src/vote/instruction.rs#L159-L164 + * + * Processor: + * https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_processor.rs#L196-L215 + */ + case fd_vote_instruction_enum_tower_sync_switch: { + FD_LOG_ERR( ( "unimplemented" ) ); + } /* Withdraw *