diff --git a/src/disco/store/fd_store.h b/src/disco/store/fd_store.h index fb8bbbd9246..d2f3fb923f3 100644 --- a/src/disco/store/fd_store.h +++ b/src/disco/store/fd_store.h @@ -346,6 +346,12 @@ static inline void fd_store_shrel( fd_store_t * store ) { fd_rwlock_unread ( &st static inline void fd_store_exacq( fd_store_t * store ) { fd_rwlock_write ( &store->lock ); } static inline void fd_store_exrel( fd_store_t * store ) { fd_rwlock_unwrite( &store->lock ); } +static inline void +fd_store_remove( fd_store_t * store, + fd_hash_t const * merkle_root ) { + (void)store; + (void)merkle_root; +} struct fd_store_lock_ctx { fd_store_t * store_; long * acq_start; diff --git a/src/discof/reasm/fd_reasm.c b/src/discof/reasm/fd_reasm.c index f4edcd641a2..57fa966cfa8 100644 --- a/src/discof/reasm/fd_reasm.c +++ b/src/discof/reasm/fd_reasm.c @@ -32,7 +32,7 @@ fd_reasm_footprint( ulong fec_max ) { bfs_align(), bfs_footprint ( fec_max ) ), out_align(), out_footprint ( fec_max ) ), bid_align(), bid_footprint ( lgf_max ) ), - xid_align(), xid_footprint ( lgf_max ) ), + xid_align(), xid_footprint ( lgf_max + 1 ) ), fd_reasm_align() ); } @@ -78,7 +78,7 @@ fd_reasm_new( void * shmem, void * bfs = FD_SCRATCH_ALLOC_APPEND( l, bfs_align(), bfs_footprint ( fec_max ) ); void * out = FD_SCRATCH_ALLOC_APPEND( l, out_align(), out_footprint ( fec_max ) ); void * bid = FD_SCRATCH_ALLOC_APPEND( l, bid_align(), bid_footprint ( lgf_max ) ); - void * xid = FD_SCRATCH_ALLOC_APPEND( l, xid_align(), xid_footprint ( lgf_max ) ); + void * xid = FD_SCRATCH_ALLOC_APPEND( l, xid_align(), xid_footprint ( lgf_max + 1 ) ); FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_reasm_align() ) == (ulong)shmem + footprint ); reasm->slot0 = ULONG_MAX; @@ -92,7 +92,7 @@ fd_reasm_new( void * shmem, reasm->bfs = bfs_new ( bfs, fec_max ); reasm->out = out_new ( out, fec_max ); reasm->bid = bid_new ( bid, lgf_max, seed ); - reasm->xid = xid_new ( xid, lgf_max, seed ); + reasm->xid = xid_new ( xid, lgf_max + 1, seed ); return shmem; } @@ -289,6 +289,276 @@ link( fd_reasm_t * reasm, } } +static void +clear_leaf( fd_reasm_t * reasm, + fd_reasm_fec_t * leaf ) { + /* see fd_forest.c clear_leaf */ + + fd_reasm_fec_t * pool = reasm_pool( reasm ); + orphaned_t * orphaned = reasm->orphaned; + frontier_t * frontier = reasm->frontier; + ancestry_t * ancestry = reasm->ancestry; + subtrees_t * subtrees = reasm->subtrees; + dlist_t * subtreel = reasm->subtreel; + + /* Clean up the parent, and remove block from the maps */ + fd_reasm_fec_t * parent = fd_reasm_parent( reasm, leaf ); + if( FD_LIKELY( parent ) ) { + leaf->parent = pool_idx_null( pool ); + /* remove the block from the parent's child list */ + fd_reasm_fec_t * child = pool_ele( pool, parent->child ); + if( FD_LIKELY( child->slot == leaf->slot ) ) { + parent->child = child->sibling; + } else { + /* go through the sibling list, and remove the block */ + fd_reasm_fec_t * sibling = pool_ele( pool, child->sibling ); + fd_reasm_fec_t * prev = child; + while( FD_LIKELY( sibling ) ) { + if( FD_LIKELY( sibling->slot == leaf->slot ) ) { + prev->sibling = sibling->sibling; + break; + } + prev = sibling; + sibling = pool_ele( pool, sibling->sibling ); + } + } + + /* remove the block itself from the maps */ + + fd_reasm_fec_t * removed = orphaned_ele_remove( orphaned, &leaf->key, NULL, pool ); + if( !removed ) { + removed = frontier_ele_remove( frontier, &leaf->key, NULL, pool ); + FD_TEST( removed ); + + /* We removed from the main tree, so we possible need to insert parent into the frontier. + Only need to add parent to the frontier if it doesn't have any other children. */ + + if( parent->child == pool_idx_null( pool ) ) { + parent = ancestry_ele_remove( ancestry, &parent->key, NULL, pool ); + FD_TEST( parent ); + frontier_ele_insert( frontier, parent, pool ); + } + } + } else { + /* remove from subtrees and subtree list */ + subtrees_ele_remove( subtrees, &leaf->key, NULL, pool ); + dlist_ele_remove ( subtreel, leaf, pool ); + } + + /* Clean up children */ + + fd_reasm_fec_t * child = pool_ele( pool, leaf->child ); + if( FD_UNLIKELY( child ) ) FD_LOG_CRIT(( "reasm_slot_leaf: cleaning up child %lu. SHOULD NOT HAVE CHIDLREN.", child->slot )); + + /* remove from bid and xid */ + int repopulate_bid = 0; int repopulate_xid = 0; + if( FD_UNLIKELY( leaf->slot_complete ) ) { + bid_t * bid = bid_query( reasm->bid, leaf->slot, NULL ); + if( FD_LIKELY( bid->idx == pool_idx( pool, leaf ) ) ) { + bid_remove( reasm->bid, bid ); + repopulate_bid = 1; + } + } + xid_t * xid = xid_query( reasm->xid, ( leaf->slot << 32 ) | leaf->fec_set_idx, NULL ); + if( FD_LIKELY( xid->idx == pool_idx( pool, leaf ) ) ) { + xid_remove( reasm->xid, xid ); + repopulate_xid = 1; + } + /* We might need to repopulate the bid and xid entries if there are + equivocating versions. This is highly inefficient, but should + happen rarely. Ideally, never. We need to traverse every single + node to find one with matching slot/fec_idx. There's is no + guarantee we will find one even if the eqvoc bit is set, as it's + possible it was an ancestor that equivocated and maybe there isn't + another version of this FEC xid */ + ulong * bfs = reasm->bfs; + if( FD_UNLIKELY( leaf->eqvoc && (repopulate_bid || repopulate_xid) ) ) { + /* add the roots of every tree so we can bfs at once */ + bfs_push_tail( bfs, reasm->root ); + for( dlist_iter_t iter = dlist_iter_fwd_init( subtreel, pool ); + !dlist_iter_done ( iter, subtreel, pool ); + iter = dlist_iter_fwd_next( iter, subtreel, pool ) ) { + bfs_push_tail( bfs, iter ); + } + + while( FD_LIKELY( !bfs_empty( bfs ) ) ) { + fd_reasm_fec_t * ele = pool_ele( pool, bfs_pop_head( bfs ) ); + if( FD_UNLIKELY( repopulate_xid && ele->slot == leaf->slot && ele->fec_set_idx == leaf->fec_set_idx ) ) { + xid_t * xid = xid_insert( reasm->xid, ( ele->slot << 32 ) | ele->fec_set_idx ); + xid->idx = pool_idx( pool, ele ); + repopulate_xid = 0; + } + if( FD_UNLIKELY( repopulate_bid && ele->slot == leaf->slot && ele->slot_complete ) ) { + bid_t * bid = bid_insert( reasm->bid, ele->slot ); + bid->idx = pool_idx( pool, ele ); + repopulate_bid = 0; + } + if( !repopulate_bid && !repopulate_xid ) break; /* early exit */ + fd_reasm_fec_t * child = fd_reasm_child( reasm, ele ); + while( FD_LIKELY( child ) ) { + bfs_push_tail( bfs, pool_idx( pool, child ) ); + child = pool_ele( pool, child->sibling ); + } + } + bfs_remove_all( bfs ); + } + + pool_ele_release( pool, leaf ); +} + +fd_reasm_fec_t * +latest_confirmed_fec( fd_reasm_t * reasm, + ulong subtree_root ) { + ulong * bfs = reasm->bfs; + fd_reasm_fec_t * pool = reasm_pool( reasm ); + bfs_push_tail( bfs, subtree_root ); + fd_reasm_fec_t * latest_confirmed = NULL; + while( FD_LIKELY( !bfs_empty( bfs ) ) ) { + fd_reasm_fec_t * ele = pool_ele( pool, bfs_pop_head( bfs ) ); + if( FD_LIKELY( ele->confirmed ) ) { + if( FD_LIKELY( latest_confirmed == NULL || + latest_confirmed->slot < ele->slot || + (latest_confirmed->slot == ele->slot && latest_confirmed->fec_set_idx < ele->fec_set_idx)) ) + latest_confirmed = ele; + } + fd_reasm_fec_t * child = fd_reasm_child( reasm, ele ); + while( FD_LIKELY( child ) ) { + bfs_push_tail( bfs, pool_idx( pool, child ) ); + child = pool_ele( pool, child->sibling ); + } + } + return NULL; +} + +static fd_reasm_fec_t * +gca( fd_reasm_t * reasm, + fd_reasm_fec_t * a, + fd_reasm_fec_t * b ) { + fd_reasm_fec_t * parent1 = a; + fd_reasm_fec_t * parent2 = b; + while( FD_LIKELY( parent1 && parent2 ) ) { + if( FD_LIKELY( parent1 == parent2 ) ) return parent1; + if( parent1->slot > parent2->slot || + ( parent1->slot == parent2->slot && parent1->fec_set_idx > parent2->fec_set_idx ) ) parent1 = fd_reasm_parent( reasm, parent1 ); + else parent2 = fd_reasm_parent( reasm, parent2 ); + } + return NULL; +} + +#define FEC_IGNORE 0 +#define FEC_INSERT 1 + +#define UPDATE_BEST_CANDIDATE( best_confrmd, best_unconfrmd, ele, filter ) \ + if( FD_UNLIKELY( filter ) ) continue; \ + do { \ + if( FD_UNLIKELY( ele->confirmed ) ) { \ + if( FD_LIKELY( !best_confrmd ) ) best_confrmd = ele; \ + else best_confrmd = fd_ptr_if( best_confrmd->slot < ele->slot, ele, best_confrmd ); \ + } else { \ + if( FD_LIKELY( !best_unconfrmd ) ) best_unconfrmd = ele; \ + else best_unconfrmd = fd_ptr_if( best_unconfrmd->slot < ele->slot, ele, best_unconfrmd ); \ + } \ + } while(0) + +static int +fd_reasm_evict( fd_reasm_t * reasm, + fd_store_t * opt_store, + fd_hash_t const * new_root FD_PARAM_UNUSED, + fd_hash_t const * parent_root, + evicted_t * evicted ) { + fd_reasm_fec_t * pool = reasm_pool( reasm ); + frontier_t * frontier = reasm->frontier; + orphaned_t * orphaned = reasm->orphaned; + subtrees_t * subtrees = reasm->subtrees; + dlist_t * subtreel = reasm->subtreel; + + /* Generally, best policy for eviction is to evict in the order of: + 1. Highest unconfirmed orphan leaf - furthest from root + 2. Highest unconfirmed leaf in ancestry - furthest from tip of execution + 3. Highest confirmed orphan leaf + 4. Highest confirmed leaf in ancestry - at this point we would not evict this candidate. + See fd_forest_evict for more details. */ + + fd_reasm_fec_t * unconfrmd_orphan = NULL; /* 1st best candidate for eviction is the highest unconfirmed orphan. */ + fd_reasm_fec_t * confirmed_orphan = NULL; /* 3rd best candidate for eviction is the highest confirmed orphan. */ + for( dlist_iter_t iter = dlist_iter_fwd_init( subtreel, pool ); + !dlist_iter_done ( iter, subtreel, pool ); + iter = dlist_iter_fwd_next( iter, subtreel, pool ) ) { + fd_reasm_fec_t * ele = dlist_iter_ele( iter, subtreel, pool ); + UPDATE_BEST_CANDIDATE( confirmed_orphan, unconfrmd_orphan, ele, ele->child != ULONG_MAX || memcmp( &ele->key, parent_root, sizeof(fd_hash_t) ) == 0 ); + } + for( orphaned_iter_t iter = orphaned_iter_init( orphaned, pool ); + !orphaned_iter_done( iter, orphaned, pool ); + iter = orphaned_iter_next( iter, orphaned, pool ) ) { + fd_reasm_fec_t * ele = orphaned_iter_ele( iter, orphaned, pool ); + UPDATE_BEST_CANDIDATE( confirmed_orphan, unconfrmd_orphan, ele, ele->child != ULONG_MAX || memcmp( &ele->key, parent_root, sizeof(fd_hash_t) ) == 0 ); + } + + fd_reasm_fec_t * unconfrmd_leaf = NULL; /* 2nd best candidate for eviction is the highest unconfirmed leaf. */ + fd_reasm_fec_t * confirmed_leaf = NULL; /* 4th best candidate for eviction is the highest confirmed leaf. */ + for( frontier_iter_t iter = frontier_iter_init( frontier, pool ); + !frontier_iter_done( iter, frontier, pool ); + iter = frontier_iter_next( iter, frontier, pool ) ) { + fd_reasm_fec_t * ele = frontier_iter_ele( iter, frontier, pool ); + UPDATE_BEST_CANDIDATE( confirmed_leaf, unconfrmd_leaf, ele, iter.ele_idx == reasm->root || memcmp( &ele->key, parent_root, sizeof(fd_hash_t) ) == 0 ); + } + + if( FD_UNLIKELY( unconfrmd_orphan )) { + if( FD_LIKELY( opt_store ) ) fd_store_remove( opt_store, &unconfrmd_orphan->key ); + evicted->mr = unconfrmd_orphan->key; + evicted->slot = unconfrmd_orphan->slot; + evicted->fec_set_idx = unconfrmd_orphan->fec_set_idx; + clear_leaf( reasm, unconfrmd_orphan ); + return FEC_INSERT; + } + if( FD_UNLIKELY( unconfrmd_leaf )) { + if( FD_LIKELY( opt_store ) ) fd_store_remove( opt_store, &unconfrmd_leaf->key ); + evicted->mr = unconfrmd_leaf->key; + evicted->slot = unconfrmd_leaf->slot; + evicted->fec_set_idx = unconfrmd_leaf->fec_set_idx; + clear_leaf( reasm, unconfrmd_leaf ); + return FEC_INSERT; + } + if( FD_UNLIKELY( confirmed_orphan )) { + fd_reasm_fec_t * parent = fd_reasm_query( reasm, parent_root ); + if( !parent ) { + if( FD_LIKELY( opt_store ) ) fd_store_remove( opt_store, &confirmed_orphan->key ); + evicted->mr = confirmed_orphan->key; + evicted->slot = confirmed_orphan->slot; + evicted->fec_set_idx = confirmed_orphan->fec_set_idx; + clear_leaf( reasm, confirmed_orphan ); + return FEC_INSERT; + } + /* for any subtree: + 0 ── 1 ── 2 ── 3 (confirmed) ── 4(confirmed) ── 5 ── 6 ──> add 7 here is valid. + └──> add 7 here is valid. + └──> add 7 here is invalid. */ + ulong subtree_root = reasm->root; + if( subtrees_ele_query( subtrees, parent_root, NULL, pool ) || + orphaned_ele_query( orphaned, parent_root, NULL, pool ) ) { + /* if adding to an orphan, find the root of the orphan subtree. */ + fd_reasm_fec_t * root = parent; + while( FD_LIKELY( root->parent != ULONG_MAX ) ) { + root = pool_ele( pool, root->parent ); + } + subtree_root = pool_idx( pool, root ); + } + + fd_reasm_fec_t * latest_confirmed_leaf = latest_confirmed_fec( reasm, subtree_root ); + if( !latest_confirmed_leaf || latest_confirmed_leaf == gca( reasm, latest_confirmed_leaf, parent )) { + clear_leaf( reasm, confirmed_orphan ); + if( FD_LIKELY( opt_store ) ) fd_store_remove( opt_store, &confirmed_orphan->key ); + return FEC_INSERT; /* is not a useless new fork. */ + } + /* is a useless new fork. */ + return FEC_IGNORE; + } + if( FD_UNLIKELY( confirmed_leaf )) { + return FEC_IGNORE; + } + return FEC_IGNORE; /* */ +} + fd_reasm_fec_t * fd_reasm_insert( fd_reasm_t * reasm, fd_hash_t const * merkle_root, @@ -299,7 +569,9 @@ fd_reasm_insert( fd_reasm_t * reasm, ushort data_cnt, int data_complete, int slot_complete, - int is_leader ) { + int is_leader, + fd_store_t * opt_store, + evicted_t * evicted_out ) { # if LOGGING FD_BASE58_ENCODE_32_BYTES( merkle_root->key, merkle_root_b58 ); @@ -309,7 +581,6 @@ fd_reasm_insert( fd_reasm_t * reasm, fd_reasm_fec_t * pool = reasm_pool( reasm ); # if FD_REASM_USE_HANDHOLDING - FD_TEST( pool_free( pool ) ); FD_TEST( !fd_reasm_query( reasm, merkle_root ) ); # endif @@ -323,6 +594,15 @@ fd_reasm_insert( fd_reasm_t * reasm, ulong * bfs = reasm->bfs; ulong * out = reasm->out; + if( FD_UNLIKELY( !pool_free( pool ) ) ){ + int rv = fd_reasm_evict( reasm, opt_store, merkle_root, chained_merkle_root, evicted_out ); + if( rv == FEC_IGNORE ) return NULL; + else { + FD_BASE58_ENCODE_32_BYTES( evicted_out->mr.uc, evmr ); + FD_BASE58_ENCODE_32_BYTES( merkle_root->uc, newmr ); + FD_LOG_NOTICE(( "evicted {slot: %lu fec: %u mr: %s} to insert {slot: %lu fec: %u mr: %s}", evicted_out->slot, evicted_out->fec_set_idx, evmr, slot, fec_set_idx, newmr )); + } + } fd_reasm_fec_t * fec = pool_ele_acquire( pool ); fec->key = *merkle_root; fec->next = null; @@ -468,8 +748,7 @@ fd_reasm_insert( fd_reasm_t * reasm, xid = xid_insert( reasm->xid, ( slot << 32 ) | fec_set_idx ); if( FD_UNLIKELY( !xid ) ) FD_LOG_CRIT(( "xid map full, slot=%lu fec_set_idx=%u", slot, fec_set_idx )); xid->idx = pool_idx( pool, fec ); - } - else { + } else { eqvoc( reasm, fec ); eqvoc( reasm, pool_ele( pool, xid->idx ) ); } diff --git a/src/discof/reasm/fd_reasm.h b/src/discof/reasm/fd_reasm.h index e65b94c6f3d..b9198bef1bd 100644 --- a/src/discof/reasm/fd_reasm.h +++ b/src/discof/reasm/fd_reasm.h @@ -38,6 +38,7 @@ pipeline. Reasm will simply deliver all the equivocating FEC sets it does observe (with a flag indicating its detection). */ +#include "../../disco/store/fd_store.h" #include "../../flamenco/types/fd_types_custom.h" /* FD_REASM_USE_HANDHOLDING: Define this to non-zero at compile time @@ -292,11 +293,23 @@ fd_reasm_pop( fd_reasm_t * reasm ); /* fd_reasm_insert inserts a new FEC set into reasm. Returns the newly inserted fd_reasm_fec_t, NULL on error. Inserting this FEC set may make one or more FEC sets available for in-order delivery. Caller - can consume these FEC sets via fd_reasm_pop. This function assumes - that the reasm is not full (fd_reasm_full() returns 0). + can consume these FEC sets via fd_reasm_pop. + + If the reasm is not full (fd_reasm_full() returns 0) the contents of + evicted_out are unchanged. If the reasm is full, reasm_insert will + evict a FEC set by the policy outlined in reasm_evict. The evicted + FEC set will be removed from reasm, and the evicted FEC metadata will + be stored in evicted_out. See top-level documentation for further details on insertion. */ +struct evicted { + fd_hash_t mr; + ulong slot; + uint fec_set_idx; +}; +typedef struct evicted evicted_t; + fd_reasm_fec_t * fd_reasm_insert( fd_reasm_t * reasm, fd_hash_t const * merkle_root, @@ -307,7 +320,9 @@ fd_reasm_insert( fd_reasm_t * reasm, ushort data_cnt, int data_complete, int slot_complete, - int leader ); + int leader, + fd_store_t * opt_store, + evicted_t * evicted_out ); /* fd_reasm_confirm confirms the FEC keyed by block_id. The ancestry beginning from this FEC then becomes the canonical chain of FEC sets diff --git a/src/discof/reasm/test_reasm.c b/src/discof/reasm/test_reasm.c index e56ee98fbb9..d63189e5c0b 100644 --- a/src/discof/reasm/test_reasm.c +++ b/src/discof/reasm/test_reasm.c @@ -40,35 +40,35 @@ test_insert( fd_wksp_t * wksp ) { fd_hash_t mr3_32[1] = {{{ 6 }}}; fd_hash_t mr3_64[1] = {{{ 7 }}}; - fd_reasm_insert( reasm, mr0_64, NULL, 0, 0, 0, 0, 0, 0, 0 ); + fd_reasm_insert( reasm, mr0_64, NULL, 0, 0, 0, 0, 0, 0, 0, NULL, NULL ); fd_reasm_fec_t * f0_64 = fd_reasm_query( reasm, mr0_64 ); FD_TEST( f0_64 ); FD_TEST( frontier_ele_query( frontier, &f0_64->key, NULL, pool ) == f0_64 ); - fd_reasm_fec_t * f1_32 = fd_reasm_insert( reasm, mr1_32, mr1_00, 1, 32, 1, 32, 1, 1, 0 ); + fd_reasm_fec_t * f1_32 = fd_reasm_insert( reasm, mr1_32, mr1_00, 1, 32, 1, 32, 1, 1, 0, NULL, NULL ); FD_TEST( subtrees_ele_query( subtrees, &f1_32->key, NULL, pool ) == f1_32 ); - fd_reasm_fec_t * f1_00 = fd_reasm_insert( reasm, mr1_00, mr0_64, 1, 0, 1, 32, 0, 0, 0 ); + fd_reasm_fec_t * f1_00 = fd_reasm_insert( reasm, mr1_00, mr0_64, 1, 0, 1, 32, 0, 0, 0, NULL, NULL ); FD_TEST( ancestry_ele_query( ancestry, &f1_00->key, NULL, pool ) ); FD_TEST( ancestry_ele_query( ancestry, &f0_64->key, NULL, pool ) ); FD_TEST( frontier_ele_query( frontier, &f1_32->key, NULL, pool ) ); - fd_reasm_fec_t * f2_00 = fd_reasm_insert( reasm, mr2_00, mr1_32, 2, 0, 1, 32, 0, 0, 0 ); + fd_reasm_fec_t * f2_00 = fd_reasm_insert( reasm, mr2_00, mr1_32, 2, 0, 1, 32, 0, 0, 0, NULL, NULL ); FD_TEST( frontier_ele_query( frontier, &f2_00->key, NULL, pool ) ); FD_TEST( ancestry_ele_query( ancestry, &f1_32->key, NULL, pool ) ); - fd_reasm_fec_t * f3_64 = fd_reasm_insert( reasm, mr3_64, mr3_32, 3, 64, 2, 32, 1, 1, 0 ); + fd_reasm_fec_t * f3_64 = fd_reasm_insert( reasm, mr3_64, mr3_32, 3, 64, 2, 32, 1, 1, 0, NULL, NULL ); FD_TEST( subtrees_ele_query( subtrees, &f3_64->key, NULL, pool ) ); - fd_reasm_fec_t * f2_32 = fd_reasm_insert( reasm, mr2_32, mr2_00, 2, 32, 1, 32, 1, 1, 0 ); + fd_reasm_fec_t * f2_32 = fd_reasm_insert( reasm, mr2_32, mr2_00, 2, 32, 1, 32, 1, 1, 0, NULL, NULL); FD_TEST( frontier_ele_query( frontier, &f2_32->key, NULL, pool ) ); FD_TEST( ancestry_ele_query( ancestry, &f2_00->key, NULL, pool ) ); - fd_reasm_fec_t * f3_32 = fd_reasm_insert( reasm, mr3_32, mr3_00, 3, 32, 2, 32, 0, 0, 0 ); + fd_reasm_fec_t * f3_32 = fd_reasm_insert( reasm, mr3_32, mr3_00, 3, 32, 2, 32, 0, 0, 0, NULL, NULL ); FD_TEST( subtrees_ele_query( subtrees, &f3_32->key, NULL, pool ) ); FD_TEST( orphaned_ele_query( orphaned, &f3_64->key, NULL, pool ) ); - fd_reasm_fec_t * f3_00 = fd_reasm_insert( reasm, mr3_00, mr1_32, 3, 0, 2, 32, 0, 0, 0 ); + fd_reasm_fec_t * f3_00 = fd_reasm_insert( reasm, mr3_00, mr1_32, 3, 0, 2, 32, 0, 0, 0, NULL, NULL ); FD_TEST( ancestry_ele_query( ancestry, &f3_00->key, NULL, pool ) ); FD_TEST( frontier_ele_query( frontier, &f2_32->key, NULL, pool ) ); FD_TEST( ancestry_ele_query( ancestry, &f3_32->key, NULL, pool ) ); @@ -91,7 +91,7 @@ test_insert( fd_wksp_t * wksp ) { parent (3, 32). */ fd_hash_t mr3_64a[1] = { { { 9 } } }; /* equivocating */ - fd_reasm_fec_t * f3_64a = fd_reasm_insert( reasm, mr3_64a, mr3_32, 3, 64, 2, 32, 1, 1, 0 ); + fd_reasm_fec_t * f3_64a = fd_reasm_insert( reasm, mr3_64a, mr3_32, 3, 64, 2, 32, 1, 1, 0, NULL, NULL ); FD_TEST( frontier_ele_query( frontier, &f3_64a->key, NULL, pool ) ); /* Equivocating first FEC set for slot 3 (mr3_0a) that chains of a @@ -99,7 +99,7 @@ test_insert( fd_wksp_t * wksp ) { off mr1_32). */ fd_hash_t mr3_0a[1] = { { { 10 } } }; - fd_reasm_fec_t * f3_0a = fd_reasm_insert( reasm, mr3_0a, mr2_32, 3, 0, 1, 32, 0, 0, 0 ); + fd_reasm_fec_t * f3_0a = fd_reasm_insert( reasm, mr3_0a, mr2_32, 3, 0, 1, 32, 0, 0, 0, NULL, NULL ); FD_TEST( frontier_ele_query( frontier, &f3_0a->key, NULL, pool ) ); fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); @@ -135,19 +135,19 @@ test_publish( fd_wksp_t * wksp ) { /* Set the root (snapshot slot). */ - fd_reasm_insert( reasm, mr0, NULL, 0, 0, 0, 0, 0, 1, 0 ); + fd_reasm_insert( reasm, mr0, NULL, 0, 0, 0, 0, 0, 1, 0, NULL, NULL ); /* Typical startup behavior, turbine orphan FECs added. */ - fd_reasm_insert( reasm, mr4, mr2, 4, 0, 2, 0, 1, 1, 0 ); - fd_reasm_insert( reasm, mr6, mr5, 6, 0, 1, 0, 1, 1, 0 ); + fd_reasm_insert( reasm, mr4, mr2, 4, 0, 2, 0, 1, 1, 0, NULL, NULL ); + fd_reasm_insert( reasm, mr6, mr5, 6, 0, 1, 0, 1, 1, 0, NULL, NULL ); /* Repairing backwards, interleaved. */ - fd_reasm_insert( reasm, mr5, mr3, 5, 0, 2, 0, 1, 1, 0 ); - fd_reasm_insert( reasm, mr2, mr1, 2, 0, 1, 0, 1, 1, 0 ); - fd_reasm_insert( reasm, mr3, mr1, 3, 0, 2, 0, 1, 1, 0 ); - fd_reasm_insert( reasm, mr1, mr0, 1, 0, 1, 0, 1, 1, 0 ); + fd_reasm_insert( reasm, mr5, mr3, 5, 0, 2, 0, 1, 1, 0, NULL, NULL ); + fd_reasm_insert( reasm, mr2, mr1, 2, 0, 1, 0, 1, 1, 0, NULL, NULL ); + fd_reasm_insert( reasm, mr3, mr1, 3, 0, 2, 0, 1, 1, 0, NULL, NULL ); + fd_reasm_insert( reasm, mr1, mr0, 1, 0, 1, 0, 1, 1, 0, NULL, NULL ); /* Check in-order delivery. */ @@ -201,19 +201,19 @@ test_slot_mr( fd_wksp_t * wksp ) { fd_hash_t mr3[1] = {{{ 3 }}}; fd_hash_t mr4[1] = {{{ 4 }}}; - fd_reasm_insert( reasm, mr1, NULL, 1, 0, 0, 0, 0, 1, 0 ); /* set root */ + fd_reasm_insert( reasm, mr1, NULL, 1, 0, 0, 0, 0, 1, 0, NULL, NULL ); /* set root */ fd_reasm_fec_t * fec1 = fd_reasm_query( reasm, mr1 ); FD_TEST( fec1 ); FD_TEST( frontier_ele_query( frontier, &fec1->key, NULL, pool ) == fec1 ); - fd_reasm_fec_t * fec2 = fd_reasm_insert( reasm, mr2, mr0, 2, 0, 1, 32, 0, 1, 0 ); /* insert with bad mr0 should be mr1 */ + fd_reasm_fec_t * fec2 = fd_reasm_insert( reasm, mr2, mr0, 2, 0, 1, 32, 0, 1, 0, NULL, NULL ); /* insert with bad mr0 should be mr1 */ FD_TEST( ancestry_ele_query( ancestry, &fec1->key, NULL, pool ) ); /* successfully chains anyways */ FD_TEST( frontier_ele_query( frontier, &fec2->key, NULL, pool ) ); - fd_reasm_fec_t * fec4 = fd_reasm_insert( reasm, mr4, mr0, 4, 0, 1, 32, 0, 1, 0 ); /* insert with bad mr0 should be mr3 */ + fd_reasm_fec_t * fec4 = fd_reasm_insert( reasm, mr4, mr0, 4, 0, 1, 32, 0, 1, 0 , NULL, NULL); /* insert with bad mr0 should be mr3 */ FD_TEST( subtrees_ele_query( subtrees, &fec4->key, NULL, pool ) ); /* orphaned */ - fd_reasm_fec_t * fec3 = fd_reasm_insert( reasm, mr3, mr2, 3, 0, 1, 32, 0, 1, 0 ); + fd_reasm_fec_t * fec3 = fd_reasm_insert( reasm, mr3, mr2, 3, 0, 1, 32, 0, 1, 0, NULL, NULL ); FD_TEST( ancestry_ele_query( ancestry, &fec3->key, NULL, pool ) ); /* mr3 should chain to 2 */ FD_TEST( frontier_ele_query( frontier, &fec4->key, NULL, pool ) ); /* mr4 should have chained */ } @@ -229,12 +229,12 @@ test_eqvoc( fd_wksp_t * wksp ) { fd_hash_t mr1a[1] = {{{ 1, 0xa }}}; fd_hash_t mr1b[1] = {{{ 1, 0xb }}}; - fd_reasm_insert( reasm, mr1, NULL, 1, 0, 1, 32, 0, 0, 0 ); + fd_reasm_insert( reasm, mr1, NULL, 1, 0, 1, 32, 0, 0, 0, NULL, NULL ); /* Slot 1 equivocates. */ - fd_reasm_insert( reasm, mr1a, mr1, /* slot */ 1, /* fec_set_idx */ 32, 1, 32, 0, 0, 0 ); - fd_reasm_insert( reasm, mr1b, mr1, /* slot */ 1, /* fec_set_idx */ 32, 1, 32, 0, 0, 0 ); + fd_reasm_insert( reasm, mr1a, mr1, /* slot */ 1, /* fec_set_idx */ 32, 1, 32, 0, 0, 0, NULL, NULL ); + fd_reasm_insert( reasm, mr1b, mr1, /* slot */ 1, /* fec_set_idx */ 32, 1, 32, 0, 0, 0, NULL, NULL ); fd_reasm_fec_t * fec1a = fd_reasm_query( reasm, mr1a ); FD_TEST( fec1a ); @@ -268,9 +268,9 @@ test_eqvoc( fd_wksp_t * wksp ) { /* Insert some descendants of mr1b. */ - fd_reasm_insert( reasm, mr1ba, mr1b, /* slot */ 1, 64, /* parent_off */ 1, /* data_cnt */ 32, /* data_complete */ 1, /* slot_complete */ 1, /* is_leader */ 0 ); - fd_reasm_insert( reasm, mr1bb, mr1b, /* slot */ 1, 64, /* parent_off */ 1, /* data_cnt */ 32, /* data_complete */ 1, /* slot_complete */ 1, /* is_leader */ 0 ); - fd_reasm_insert( reasm, mr2, mr1bb, /* slot */ 2, 0, /* parent_off */ 1, /* data_cnt */ 32, /* data_complete */ 0, /* slot_complete */ 0, /* is_leader */ 0 ); + fd_reasm_insert( reasm, mr1ba, mr1b, /* slot */ 1, 64, /* parent_off */ 1, /* data_cnt */ 32, /* data_complete */ 1, /* slot_complete */ 1, /* is_leader */ 0, NULL, NULL ); + fd_reasm_insert( reasm, mr1bb, mr1b, /* slot */ 1, 64, /* parent_off */ 1, /* data_cnt */ 32, /* data_complete */ 1, /* slot_complete */ 1, /* is_leader */ 0, NULL, NULL ); + fd_reasm_insert( reasm, mr2, mr1bb, /* slot */ 2, 0, /* parent_off */ 1, /* data_cnt */ 32, /* data_complete */ 0, /* slot_complete */ 0, /* is_leader */ 0, NULL, NULL ); /* mr1ba and mr1bb should not be delivered by pop even though they are chained to mr1b because they equivocate and are not valid. */ @@ -309,19 +309,19 @@ test_eqvoc_transitive( fd_wksp_t * wksp ) { fd_hash_t mr4[1] = {{{ 4 }}}; fd_hash_t mr5[1] = {{{ 5 }}}; - fd_reasm_insert( reasm, mr1, NULL, /* slot */ 1, /* fec_set_idx */ 0, 1, 32, 0, 0, 0 ); - fd_reasm_insert( reasm, mr2a, mr1, /* slot */ 2, /* fec_set_idx */ 32, 1, 32, 0, 0, 0 ); - fd_reasm_insert( reasm, mr3, mr2aa, /* slot */ 3, /* fec_set_idx */ 0, 1, 32, 0, 0, 0 ); - fd_reasm_insert( reasm, mr4, mr2aa, /* slot */ 4, /* fec_set_idx */ 0, 1, 32, 0, 0, 0 ); + fd_reasm_insert( reasm, mr1, NULL, /* slot */ 1, /* fec_set_idx */ 0, 1, 32, 0, 0, 0, NULL, NULL ); + fd_reasm_insert( reasm, mr2a, mr1, /* slot */ 2, /* fec_set_idx */ 32, 1, 32, 0, 0, 0, NULL, NULL ); + fd_reasm_insert( reasm, mr3, mr2aa, /* slot */ 3, /* fec_set_idx */ 0, 1, 32, 0, 0, 0, NULL, NULL ); + fd_reasm_insert( reasm, mr4, mr2aa, /* slot */ 4, /* fec_set_idx */ 0, 1, 32, 0, 0, 0, NULL, NULL ); /* Introduce 2b (which equivocates with 2a). */ - fd_reasm_insert( reasm, mr2b, mr1, /* slot */ 2, /* fec_set_idx */ 32, 1, 32, 0, 0, 0 ); + fd_reasm_insert( reasm, mr2b, mr1, /* slot */ 2, /* fec_set_idx */ 32, 1, 32, 0, 0, 0, NULL, NULL ); /* Introduce 2aa, which un-orphans 3 and 4. This should transitively mark 2aa, 3 and 4 all as equivocating because 2a equivocates. */ - fd_reasm_insert( reasm, mr2aa, mr2a, /* slot */ 2, /* fec_set_idx */ 64, 1, 32, 0, 1, 0 ); + fd_reasm_insert( reasm, mr2aa, mr2a, /* slot */ 2, /* fec_set_idx */ 64, 1, 32, 0, 1, 0, NULL, NULL ); FD_TEST( !fd_reasm_query( reasm, mr1 )->eqvoc ); FD_TEST( fd_reasm_query( reasm, mr2a )->eqvoc ); @@ -344,11 +344,69 @@ test_eqvoc_transitive( fd_wksp_t * wksp ) { /* Insert 4, which is a child of 3. Even though 3 is eqvoc, 4 should not be eqvoc because 3 is confirmed. */ - fd_reasm_insert( reasm, mr5, mr4, /* slot */ 5, /* fec_set_idx */ 0, 1, 32, 0, 0, 0 ); + fd_reasm_insert( reasm, mr5, mr4, /* slot */ 5, /* fec_set_idx */ 0, 1, 32, 0, 0, 0, NULL, NULL ); FD_TEST( !fd_reasm_query( reasm, mr5 )->eqvoc ); FD_TEST( !fd_reasm_query( reasm, mr5 )->confirmed ); fd_reasm_print( reasm ); +} + +void +test_evict( fd_wksp_t * wksp ) { + ulong fec_max = 8; + void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); + fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); + + + FD_LOG_NOTICE( ( "xid key max %lu", xid_key_max( reasm->xid ) )); + FD_LOG_NOTICE( ( "xid slot cnt %lu", xid_slot_cnt( reasm->xid ) )); + FD_TEST( reasm ); + + fd_hash_t mr0[1] = {{{ 0 }}}; fd_hash_t mr9 [1] = {{{ 9 }}}; + fd_hash_t mr1[1] = {{{ 1 }}}; + fd_hash_t mr2[1] = {{{ 2 }}}; + fd_hash_t mr3[1] = {{{ 3 }}}; + fd_hash_t mr4[1] = {{{ 4 }}}; + fd_hash_t mr5[1] = {{{ 5 }}}; + fd_hash_t mr6[1] = {{{ 6 }}}; + fd_hash_t mr7[1] = {{{ 7 }}}; + fd_hash_t mr8[1] = {{{ 8 }}}; + + (void)mr6; + (void)mr7; + (void)mr8; + (void)mr9; + + evicted_t evicted[1]; + /* mr cmr slot fec_idx p_off data_cnt - slot_cmpl - - */ + FD_TEST( fd_reasm_insert( reasm, mr0, NULL, 1, 0, 1, 32, 0, 1, 0, NULL, evicted ) ); + FD_TEST( fd_reasm_insert( reasm, mr1, mr0, 2, 0, 1, 32, 0, 1, 0, NULL, evicted ) ); + FD_TEST( fd_reasm_insert( reasm, mr2, mr1, 3, 0, 1, 32, 0, 1, 0, NULL, evicted ) ); + /* fork 4 and 5 off 3*/ + FD_TEST( fd_reasm_insert( reasm, mr3, mr2, 4, 0, 1, 32, 0, 0, 0, NULL, evicted ) ); + FD_TEST( fd_reasm_insert( reasm, mr4, mr2, 5, 0, 1, 32, 0, 0, 0, NULL, evicted ) ); + FD_TEST( fd_reasm_insert( reasm, mr5, mr4, 5, 32, 1, 32, 0, 1, 0, NULL, evicted ) ); + + /* now for orphans */ + FD_TEST( fd_reasm_insert( reasm, mr8, mr7, 8, 0, 1, 32, 0, 0, 0, NULL, evicted ) ); + FD_TEST( fd_reasm_insert( reasm, mr9, mr8, 8, 0, 1, 32, 0, 1, 0, NULL, evicted ) ); /* By the way maybe this should be rejected? agave will reject it. But lowkeys we would replay it.... */ + + /* should be full */ + fd_reasm_print( reasm ); + + /* evict and unconfirmed orphan */ + FD_TEST( fd_reasm_insert( reasm, mr6, mr5, 6, 0, 1, 32, 0, 0, 0, NULL, evicted ) ); + FD_TEST( !fd_reasm_query( reasm, mr9 )); /* evicts unconfirmed orphan */ + FD_TEST( evicted->slot == 8 && evicted->fec_set_idx == 0 ); + + /* evict an unconfirmed leaf */ + FD_TEST( fd_reasm_insert( reasm, mr9, mr8, 8, 32, 1, 32, 0, 1, 0, NULL, evicted ) ); + FD_TEST( !fd_reasm_query( reasm, mr6 )); /* evicts unconfirmed leaf */ + FD_TEST( evicted->slot == 6 && evicted->fec_set_idx == 0 ); + + + + } int @@ -366,6 +424,7 @@ main( int argc, char ** argv ) { //test_slot_mr( wksp ); //test_eqvoc( wksp ); test_eqvoc_transitive( wksp ); + test_evict( wksp ); fd_halt(); return 0; diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c index 0cb7f0373a2..a6327904b89 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -1381,7 +1381,7 @@ boot_genesis( fd_replay_tile_t * ctx, maybe_become_leader( ctx, stem ); fd_hash_t initial_block_id = { .ul = { FD_RUNTIME_INITIAL_BLOCK_ID } }; - fd_reasm_fec_t * fec = fd_reasm_insert( ctx->reasm, &initial_block_id, NULL, 0 /* genesis slot */, 0, 0, 0, 0, 1, 0 ); /* FIXME manifest block_id */ + fd_reasm_fec_t * fec = fd_reasm_insert( ctx->reasm, &initial_block_id, NULL, 0 /* genesis slot */, 0, 0, 0, 0, 1, 0, ctx->store, NULL ); /* FIXME manifest block_id */ fec->bank_idx = 0UL; @@ -1506,7 +1506,7 @@ on_snapshot_message( fd_replay_tile_t * ctx, publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */ ); publish_root_advanced( ctx, stem ); - fd_reasm_fec_t * fec = fd_reasm_insert( ctx->reasm, &manifest_block_id, NULL, snapshot_slot, 0, 0, 0, 0, 1, 0 ); /* FIXME manifest block_id */ + fd_reasm_fec_t * fec = fd_reasm_insert( ctx->reasm, &manifest_block_id, NULL, snapshot_slot, 0, 0, 0, 0, 1, 0, ctx->store, NULL ); /* FIXME manifest block_id */ fec->bank_idx = 0UL; ctx->cluster_type = fd_bank_cluster_type_get( bank ); @@ -2209,14 +2209,12 @@ process_fec_complete( fd_replay_tile_t * ctx, int data_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE ); int slot_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE ); - FD_TEST( !fd_reasm_query( ctx->reasm, merkle_root ) ); + FD_TEST( !fd_reasm_query( ctx->reasm, merkle_root ) ); // TODO no longer impossible if( FD_UNLIKELY( shred->slot - shred->data.parent_off == fd_reasm_slot0( ctx->reasm ) && shred->fec_set_idx == 0) ) { chained_merkle_root = &fd_reasm_root( ctx->reasm )->key; } - FD_TEST( fd_reasm_free( ctx->reasm ) ); - - FD_TEST( fd_reasm_insert( ctx->reasm, merkle_root, chained_merkle_root, shred->slot, shred->fec_set_idx, shred->data.parent_off, (ushort)(shred->idx - shred->fec_set_idx + 1), data_complete, slot_complete, is_leader_fec ) ); + fd_reasm_insert( ctx->reasm, merkle_root, chained_merkle_root, shred->slot, shred->fec_set_idx, shred->data.parent_off, (ushort)(shred->idx - shred->fec_set_idx + 1), data_complete, slot_complete, is_leader_fec, ctx->store, NULL ); } static void