Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/disco/store/fd_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
293 changes: 286 additions & 7 deletions src/discof/reasm/fd_reasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
}

Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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,
Expand All @@ -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 );
Expand All @@ -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

Expand All @@ -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;
Expand Down Expand Up @@ -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 ) );
}
Expand Down
Loading