diff --git a/src/flamenco/runtime/fd_runtime_const.h b/src/flamenco/runtime/fd_runtime_const.h index 34cc0066a5c..bc8f275869f 100644 --- a/src/flamenco/runtime/fd_runtime_const.h +++ b/src/flamenco/runtime/fd_runtime_const.h @@ -292,6 +292,8 @@ FD_STATIC_ASSERT( BPF_LOADER_SERIALIZATION_FOOTPRINT==FD_BPF_LOADER_INPUT_REGION #define FD_VOTE_LOCKOUTS_ALIGN (32UL) #define FD_VOTE_LOCKOUTS_FOOTPRINT (FD_VOTE_STATE_VERSIONED_FOOTPRINT) +#define FD_MAX_WRITALBE_ACCOUNTS_IN_SLOT (42000UL) + static const FD_FN_UNUSED fd_account_meta_t FD_ACCOUNT_META_DEFAULT = {0}; FD_PROTOTYPES_END diff --git a/src/flamenco/stakes/Local.mk b/src/flamenco/stakes/Local.mk index 6892777f8e6..2a43da972a9 100644 --- a/src/flamenco/stakes/Local.mk +++ b/src/flamenco/stakes/Local.mk @@ -12,7 +12,12 @@ endif $(call add-hdrs,fd_vote_states.h) $(call add-objs,fd_vote_states,fd_flamenco) +$(call add-hdrs,fd_vote_timestamps.h) +$(call add-objs,fd_vote_timestamps,fd_flamenco) ifdef FD_HAS_HOSTED $(call make-unit-test,test_vote_states,test_vote_states,fd_flamenco fd_funk fd_ballet fd_util) $(call run-unit-test,test_vote_states) + +$(call make-unit-test,test_vote_timestamps,test_vote_timestamps,fd_flamenco fd_funk fd_ballet fd_util) +$(call run-unit-test,test_vote_timestamps) endif diff --git a/src/flamenco/stakes/fd_vote_timestamps.c b/src/flamenco/stakes/fd_vote_timestamps.c new file mode 100644 index 00000000000..be55d79d0a4 --- /dev/null +++ b/src/flamenco/stakes/fd_vote_timestamps.c @@ -0,0 +1,606 @@ +#include "fd_vote_timestamps.h" +#include "fd_vote_timestamps_private.h" + +ulong +fd_vote_timestamps_align( void ) { + return 128UL; +} + +ulong +fd_vote_timestamps_footprint( ulong max_live_slots, + uchar max_snaps, + ulong max_vote_accs ) { + ulong map_chain_cnt = 2048UL; + + ulong l = FD_LAYOUT_INIT; + l = FD_LAYOUT_APPEND( l, fd_vote_timestamps_align(), sizeof(fd_vote_timestamps_t) ); + l = FD_LAYOUT_APPEND( l, fork_pool_align(), fork_pool_footprint( max_live_slots ) ); + l = FD_LAYOUT_APPEND( l, index_pool_align(), index_pool_footprint( max_vote_accs ) ); + l = FD_LAYOUT_APPEND( l, index_map_align(), index_map_footprint( map_chain_cnt ) ); + l = FD_LAYOUT_APPEND( l, snapshot_key_dlist_align(), snapshot_key_dlist_footprint() ); + l = FD_LAYOUT_APPEND( l, snapshot_key_pool_align(), snapshot_key_pool_footprint( max_snaps ) ); + for( uchar i=0; ifork_pool_offset = (ulong)fork_pool - (ulong)shmem; + vote_timestamps->index_pool_offset = (ulong)index_pool - (ulong)shmem; + vote_timestamps->index_map_offset = (ulong)index_map - (ulong)shmem; + + + snapshot_key_ele_t * snapshot_keys_pool = snapshot_key_pool_join( snapshot_key_pool_new( snapshot_keys_pool_mem, max_snaps ) ); + if( FD_UNLIKELY( !snapshot_keys_pool ) ) { + FD_LOG_WARNING(( "Failed to create vote timestamp snapshot keys pool" )); + return NULL; + } + + snapshot_key_dlist_t * snapshot_keys = snapshot_key_dlist_join( snapshot_key_dlist_new( snapshot_keys_dlist_mem ) ); + if( FD_UNLIKELY( !snapshot_keys ) ) { + FD_LOG_WARNING(( "Failed to create vote timestamp snapshot keys" )); + return NULL; + } + + for( uchar i=0; ioffset = (ulong)snapshots_mem - (ulong)vote_timestamps; + + snapshot_map_t * snapshot_map = snapshot_map_join( snapshot_map_new( snapshots_ele_map_mem, map_chain_cnt, seed ) ); + if( FD_UNLIKELY( !snapshot_map ) ) { + FD_LOG_WARNING(( "Failed to create vote timestamp snapshot ele map" )); + return NULL; + } + + key->map_offset = (ulong)snapshot_map - (ulong)vote_timestamps; + } + for( uchar i=0; isnapshot_keys_dlist_offset = (ulong)snapshot_keys - (ulong)shmem; + vote_timestamps->snapshot_keys_pool_offset = (ulong)snapshot_keys_pool - (ulong)shmem; + + return shmem; +} + +fd_vote_timestamps_t * +fd_vote_timestamps_join( void * shmem ) { + return (fd_vote_timestamps_t *)shmem; +} + +ushort +fd_vote_timestamps_init( fd_vote_timestamps_t * vote_ts, + ulong slot, + ushort epoch ) { + /* Assign a fork node on the fork pool */ + fork_ele_t * pool = fd_vote_timestamps_get_fork_pool( vote_ts ); + fork_ele_t * fork = fork_pool_ele_acquire( pool ); + ushort fork_idx = (ushort)fork_pool_idx( pool, fork ); + + vote_ts->root_idx = fork_idx; + fork->parent_idx = USHORT_MAX; + fork->child_idx = USHORT_MAX; + fork->sibling_idx = USHORT_MAX; + fork->slot = slot; + fork->epoch = epoch; + + /* Setup the snapshot key for the root fork. */ + + snapshot_key_ele_t * snapshot_keys_pool = fd_vote_timestamps_get_snapshot_keys_pool( vote_ts ); + + snapshot_key_dlist_t * snapshot_keys_dlist = fd_vote_timestamps_get_snapshot_keys_dlist( vote_ts ); + snapshot_key_ele_t * new_key = snapshot_key_pool_ele_acquire( snapshot_keys_pool ); + ulong sidx = snapshot_key_pool_idx( snapshot_keys_pool, new_key ); + fork->snapshot_idx = (uchar)sidx; + FD_LOG_WARNING(("ROOT SNAPSHOT KEY IDX: %u", (uchar)sidx)); + + snapshot_key_dlist_ele_push_tail( snapshot_keys_dlist, new_key, snapshot_keys_pool ); + + /* Now that the node is on the tracking dlist and is allocated we + need to initialize the map for the snapshot. */ + snapshot_map_t * snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_ts, fork->snapshot_idx ); + snapshot_map_reset( snapshot_map ); + + return fork_idx; +} + +ushort +fd_vote_timestamps_attach_child( fd_vote_timestamps_t * vote_ts, + ushort parent_fork_idx, + ulong slot, + ushort epoch ) { + + fork_ele_t * pool = fd_vote_timestamps_get_fork_pool( vote_ts ); + + FD_CRIT( fork_pool_free( pool )!=0UL, "No free slots in vote timestamp pool" ); + + fork_ele_t * child = fork_pool_ele_acquire( pool ); + ushort child_idx = (ushort)fork_pool_idx( pool, child ); + + + fork_ele_t * parent = fork_pool_ele( pool, parent_fork_idx ); + FD_CRIT( parent, "parent fork idx not found" ); + + child->parent_idx = parent_fork_idx; + FD_LOG_NOTICE(("CHILD IDX %u PARENT IDX %u", child_idx, child->parent_idx)); + + if( FD_LIKELY( parent->child_idx==USHORT_MAX ) ) { + parent->child_idx = child_idx; + } else { + fork_ele_t * curr = fork_pool_ele( pool, parent->child_idx ); + /* Assign child as the sibling pointer of rightmost child. */ + while( curr->sibling_idx!=USHORT_MAX ) { + curr = fork_pool_ele( pool, curr->sibling_idx ); + } + curr->sibling_idx = child_idx; + } + + child->sibling_idx = USHORT_MAX; + child->child_idx = USHORT_MAX; + child->slot = slot; + child->epoch = epoch; + child->deltas_cnt = 0UL; + child->snapshot_idx = UCHAR_MAX; + + return child_idx; +} + +static void +apply_root_delta( fd_vote_timestamps_t * vote_ts, + fork_ele_t * new_root, + fork_ele_t * old_root ) { + + index_ele_t * index_pool = fd_vote_timestamps_get_index_pool( vote_ts ); + + snapshot_map_t * snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_ts, old_root->snapshot_idx ); + snapshot_ele_t * snapshot = fd_vote_timestamps_get_snapshot( vote_ts, old_root->snapshot_idx ); + + /* NOTE: After this point, the snapshot has incorrect slot age, but + I think this is okay because slot age gets overriden anyway? + TODO:FIXME: confirm that this is true. */ + for( ushort i=0; ideltas_cnt; i++ ) { + /* We have the property that timestamps are always increasing for + the same pubkey. When a pubkey is evicted from the index, then + we will clear all entries for that pubkey in all snapshots in the + case it gets renewed. */ + delta_ele_t * delta = &new_root->deltas[i]; + + index_ele_t * ele = index_pool_ele( index_pool, delta->pubkey_idx ); + ele->refcnt--; + + snapshot_ele_t * snapshot_ele = snapshot_map_ele_query( snapshot_map, &delta->pubkey_idx, NULL, snapshot ); + if( FD_LIKELY( snapshot_ele ) ) { + snapshot_ele->idx = delta->pubkey_idx; + snapshot_ele->timestamp = delta->timestamp; + snapshot_ele->slot_age = new_root->slot - old_root->slot; + } else { + snapshot_ele = &snapshot[delta->pubkey_idx]; + snapshot_ele->idx = delta->pubkey_idx; + snapshot_ele->timestamp = delta->timestamp; + snapshot_ele->slot_age = new_root->slot - old_root->slot; + snapshot_map_ele_insert( snapshot_map, snapshot_ele, snapshot ); + } + } + /* We no longer need the deltas here so we can clear them. */ + new_root->deltas_cnt = 0; +} + +void +fd_vote_timestamps_advance_root( fd_vote_timestamps_t * vote_ts, + ushort new_root_idx ) { + fork_ele_t * pool = fd_vote_timestamps_get_fork_pool( vote_ts ); + fork_ele_t * new_root = fork_pool_ele( pool, new_root_idx ); + fork_ele_t * head = fork_pool_ele( pool, vote_ts->root_idx ); + + FD_CRIT( new_root->parent_idx==vote_ts->root_idx, "new root is not a child of the current root" ); + + /* The new root node can either have a snapshot or not. If the new + root does, we can go ahead and free the snapshot the root has. + We can also update the root pointer. */ + snapshot_key_ele_t * snapshot_keys_pool = fd_vote_timestamps_get_snapshot_keys_pool( vote_ts ); + snapshot_key_ele_t * old_root_key = snapshot_key_pool_ele( snapshot_keys_pool, head->snapshot_idx ); + if( FD_UNLIKELY( new_root->snapshot_idx!=UCHAR_MAX ) ) { + old_root_key->fork_idx = USHORT_MAX; + head->snapshot_idx = UCHAR_MAX; + + new_root->deltas_cnt = 0; + + snapshot_key_dlist_t * dlist = fd_vote_timestamps_get_snapshot_keys_dlist( vote_ts ); + snapshot_key_dlist_idx_remove( dlist, head->snapshot_idx, snapshot_keys_pool ); + snapshot_key_pool_ele_release( snapshot_keys_pool, old_root_key ); + } else { + /* This means that we need to apply the delta from the new root onto + the old root's snapshot. Then transfer ownership of the snapshot + to the new_root. */ + FD_LOG_WARNING(("ADVANCING ROOT TO NODE WITHOUT SNAPSHOT")); + apply_root_delta( vote_ts, new_root, head ); + + old_root_key->fork_idx = new_root_idx; + new_root->snapshot_idx = head->snapshot_idx; + head->snapshot_idx = UCHAR_MAX; + } + + snapshot_ele_t * snapshot = fd_vote_timestamps_get_snapshot( vote_ts, new_root->snapshot_idx ); + snapshot_map_t * snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_ts, new_root->snapshot_idx ); + + head->next = USHORT_MAX; + fork_ele_t * tail = head; + while( head ) { + fork_ele_t * child = fork_pool_ele( pool, head->child_idx ); + + while( FD_LIKELY( child ) ) { + + if( FD_LIKELY( child!=new_root ) ) { + + /* Update tail pointers */ + tail->next = (ushort)fork_pool_idx( pool, child ); + tail = fork_pool_ele( pool, tail->next ); + tail->next = USHORT_MAX; + } + + child = fork_pool_ele( pool, child->sibling_idx ); + } + + fork_ele_t * next = fork_pool_ele( pool, head->next ); + + if( FD_UNLIKELY( head->snapshot_idx!=UCHAR_MAX ) ) { + snapshot_key_ele_t * key = snapshot_key_pool_ele( snapshot_keys_pool, head->snapshot_idx ); + key->fork_idx = USHORT_MAX; + snapshot_key_pool_ele_release( snapshot_keys_pool, key ); + head->snapshot_idx = UCHAR_MAX; + snapshot_key_dlist_t * dlist = fd_vote_timestamps_get_snapshot_keys_dlist( vote_ts ); + snapshot_key_dlist_idx_remove( dlist, head->snapshot_idx, snapshot_keys_pool ); + } + + index_ele_t * index_pool = fd_vote_timestamps_get_index_pool( vote_ts ); + for( ushort i=0; ideltas_cnt; i++ ) { + delta_ele_t * delta = &head->deltas[i]; + index_ele_t * ele = index_pool_ele( index_pool, delta->pubkey_idx ); + ele->refcnt--; + /* Release the index entry if it's not in the root's snapshot and + no longer has any references. */ + if( FD_UNLIKELY( ele->refcnt==0 && snapshot_map_idx_query( snapshot_map, &delta->pubkey_idx, UINT_MAX, snapshot )==UINT_MAX ) ) { + index_pool_ele_release( index_pool, ele ); + } + } + head->deltas_cnt = 0; + + FD_LOG_WARNING(("ELEMENT RELEASE")); + + fork_pool_ele_release( pool, head ); + head = next; + } + + new_root->parent_idx = USHORT_MAX; + vote_ts->root_idx = new_root_idx; + + FD_LOG_WARNING(("VOTE TS %u", vote_ts->root_idx)); + + snapshot = fd_vote_timestamps_get_snapshot( vote_ts, new_root->snapshot_idx ); + snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_ts, new_root->snapshot_idx ); + + for( snapshot_map_iter_t iter = snapshot_map_iter_init( snapshot_map, snapshot ); + !snapshot_map_iter_done( iter, snapshot_map, snapshot ); + iter = snapshot_map_iter_next( iter, snapshot_map, snapshot ) ) { + snapshot_ele_t * ele = snapshot_map_iter_ele( iter, snapshot_map, snapshot ); + FD_LOG_NOTICE(( "timestamp: %lu", ele->timestamp )); + } + + + /* Now clear all evictable entries from the index. + TODO:FIXME: implement another map ontop of the index pool to track + entries that are candidates for eviction. */ +} + +void +fd_vote_timestamps_insert( fd_vote_timestamps_t * vote_ts, + ushort fork_idx, + fd_pubkey_t pubkey, + ulong timestamp, + ulong stake ) { + /* First update and query index. Figure out pubkey index if not one + exists, otherwise allocate a new entry in the index. */ + index_ele_t * index_pool = fd_vote_timestamps_get_index_pool( vote_ts ); + index_map_t * index_map = fd_vote_timestamps_get_index_map( vote_ts ); + + fork_ele_t * fork_pool = fd_vote_timestamps_get_fork_pool( vote_ts ); + fork_ele_t * fork = fork_pool_ele( fork_pool, fork_idx ); + + index_ele_t * ele = index_map_ele_query( index_map, &pubkey, NULL, index_pool ); + if( FD_LIKELY( ele ) ) { + ele->refcnt++; + } else { + FD_LOG_NOTICE(("INSERTING NEW ELE")); + ele = index_pool_ele_acquire( index_pool ); + ele->pubkey = pubkey; + ele->refcnt = 1UL; + ele->epoch_stakes[ fork->epoch % 2UL ] = stake; + FD_TEST( index_map_ele_insert( index_map, ele, index_pool ) ); + } + + uint pubkey_idx = (uint)index_pool_idx( index_pool, ele ); + + /* Now just add the entry to the delta list. */ + delta_ele_t * delta = &fork->deltas[ fork->deltas_cnt ]; + delta->timestamp = timestamp; + delta->pubkey_idx = pubkey_idx; + fork->deltas_cnt++; + +} + +void +fd_vote_timestamps_insert_root( fd_vote_timestamps_t * vote_ts, + fd_pubkey_t pubkey, + ulong timestamp, + ulong stake ) { + + /* First update and query index. Figure out pubkey index if not one + exists, otherwise allocate a new entry in the index. */ + index_ele_t * index_pool = fd_vote_timestamps_get_index_pool( vote_ts ); + index_map_t * index_map = fd_vote_timestamps_get_index_map( vote_ts ); + + fork_ele_t * fork_pool = fd_vote_timestamps_get_fork_pool( vote_ts ); + fork_ele_t * fork = fork_pool_ele( fork_pool, vote_ts->root_idx ); + + index_ele_t * ele = index_pool_ele_acquire( index_pool ); + ele->pubkey = pubkey; + ele->refcnt = 0UL; + ele->epoch_stakes[ fork->epoch % 2UL ] = stake; + + FD_TEST( index_map_ele_insert( index_map, ele, index_pool ) ); + uint pubkey_idx = (uint)index_pool_idx( index_pool, ele ); + + snapshot_ele_t * snapshot = fd_vote_timestamps_get_snapshot( vote_ts, fork->snapshot_idx ); + snapshot_map_t * snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_ts, fork->snapshot_idx ); + + snapshot_ele_t * snapshot_ele = &snapshot[pubkey_idx]; + snapshot_ele->idx = pubkey_idx; + snapshot_ele->timestamp = timestamp; + snapshot_ele->slot_age = 0; + snapshot_map_ele_insert( snapshot_map, snapshot_ele, snapshot ); +} + +static uchar +prune_and_get_snapshot( fd_vote_timestamps_t * vote_ts, + ushort fork_idx, + ushort * parent_snapshot_path, + ushort * parent_snapshot_path_cnt ) { + /* A reasonable eviction policy here is LRU eviction with some tweaks: + 1. Never evict the root snapshot + 2. Don't evict the "best" snapshot (closest to the fork idx) */ + fork_ele_t * fork_pool = fd_vote_timestamps_get_fork_pool( vote_ts ); + fork_ele_t * fork = fork_pool_ele( fork_pool, fork_idx ); + fork_ele_t * root = fork_pool_ele( fork_pool, vote_ts->root_idx ); + + /* Find best snapshot to build off of. Always prioritize the least + amount of deltas. This is purely a policy decision. */ + parent_snapshot_path[ *parent_snapshot_path_cnt ] = (ushort)fork_pool_idx( fork_pool, fork ); + (*parent_snapshot_path_cnt)++; + + fork_ele_t * curr = fork; + while( curr->snapshot_idx==UCHAR_MAX ) { + curr = fork_pool_ele( fork_pool, curr->parent_idx ); + parent_snapshot_path[*parent_snapshot_path_cnt] = (ushort)fork_pool_idx( fork_pool, curr ); + (*parent_snapshot_path_cnt)++; + } + + uchar best_snapshot_idx = curr->snapshot_idx; + uchar root_snapshot_idx = root->snapshot_idx; + + FD_LOG_NOTICE(("PATH CNT %u BEST %u, ROOT %u", *parent_snapshot_path_cnt, best_snapshot_idx, root_snapshot_idx)); + + snapshot_key_dlist_t * snapshot_keys_dlist = fd_vote_timestamps_get_snapshot_keys_dlist( vote_ts ); + snapshot_key_ele_t * snapshot_keys_pool = fd_vote_timestamps_get_snapshot_keys_pool( vote_ts ); + + if( FD_UNLIKELY( snapshot_key_pool_free( snapshot_keys_pool )==0UL ) ) { + /* If there are no free slots in the pool, we need to evict. */ + + snapshot_key_ele_t * key = snapshot_key_dlist_ele_pop_head( snapshot_keys_dlist, snapshot_keys_pool ); + uchar idx = (uchar)snapshot_key_pool_idx( snapshot_keys_pool, key ); + /* TODO: MAKE IT SO THE ROOTED BANK ISN'T IN THE DLIST */ + if( idx==root_snapshot_idx || idx==best_snapshot_idx ) { + snapshot_key_dlist_ele_push_tail( snapshot_keys_dlist, key, snapshot_keys_pool ); + key = snapshot_key_dlist_ele_pop_head( snapshot_keys_dlist, snapshot_keys_pool ); + idx = (uchar)snapshot_key_pool_idx( snapshot_keys_pool, key ); + } + if( idx==root_snapshot_idx || idx==best_snapshot_idx ) { + snapshot_key_dlist_ele_push_tail( snapshot_keys_dlist, key, snapshot_keys_pool ); + key = snapshot_key_dlist_ele_pop_head( snapshot_keys_dlist, snapshot_keys_pool ); + idx = (uchar)snapshot_key_pool_idx( snapshot_keys_pool, key ); + } + FD_LOG_NOTICE(("EVICTED KEY IDX: %u", idx)); + fork_ele_t * fork = fork_pool_ele( fork_pool, key->fork_idx ); + fork->snapshot_idx = UCHAR_MAX; + key->fork_idx = USHORT_MAX; + snapshot_key_pool_ele_release( snapshot_keys_pool, key ); + } + + snapshot_key_ele_t * new_key = snapshot_key_pool_ele_acquire( snapshot_keys_pool ); + new_key->fork_idx = fork_idx; + FD_LOG_NOTICE(("SNAPSHOT KEY IDX: %u", (uchar)snapshot_key_pool_idx( snapshot_keys_pool, new_key ))); + snapshot_key_dlist_ele_push_tail( snapshot_keys_dlist, new_key, snapshot_keys_pool ); + return (uchar)snapshot_key_pool_idx( snapshot_keys_pool, new_key ); +} + +static void +apply_snapshot( snapshot_ele_t * snapshot, + snapshot_map_t * snapshot_map, + ulong base_slot, + snapshot_ele_t * prev_snapshot, + snapshot_map_t * prev_snapshot_map, + ulong prev_slot ) { + + for( snapshot_map_iter_t iter = snapshot_map_iter_init( prev_snapshot_map, prev_snapshot ); + !snapshot_map_iter_done( iter, prev_snapshot_map, prev_snapshot ); + iter = snapshot_map_iter_next( iter, prev_snapshot_map, prev_snapshot ) ) { + uint ele_idx = (uint)snapshot_map_iter_idx( iter, prev_snapshot_map, prev_snapshot ); + + snapshot_ele_t * snapshot_ele = snapshot_map_ele_query( snapshot_map, &ele_idx, NULL, snapshot ); + if( FD_LIKELY( snapshot_ele ) ) continue; + snapshot_ele = &snapshot[ele_idx]; + snapshot_ele->idx = (uint)ele_idx; + snapshot_ele->timestamp = prev_snapshot[ele_idx].timestamp; + snapshot_ele->slot_age = base_slot - prev_slot; + snapshot_map_ele_insert( snapshot_map, snapshot_ele, snapshot ); + } +} + +static void +apply_delta( ulong base_slot, + snapshot_ele_t * snapshot, + snapshot_map_t * snapshot_map, + fork_ele_t * fork ) { + + FD_LOG_NOTICE(("APPLYING DELTAS %u", fork->deltas_cnt)); + for( ushort i=0; ideltas_cnt; i++ ) { + /* We have the property that timestamps are always increasing for + the same pubkey. When a pubkey is evicted from the index, then + we will clear all entries for that pubkey in all snapshots in the + case it gets renewed. */ + delta_ele_t * delta = &fork->deltas[i]; + snapshot_ele_t * snapshot_ele = snapshot_map_ele_query( snapshot_map, &delta->pubkey_idx, NULL, snapshot ); + if( FD_UNLIKELY( snapshot_ele ) ) { + /* If it is already found do nothing */ + } else { + snapshot_ele = &snapshot[delta->pubkey_idx]; + snapshot_ele->idx = delta->pubkey_idx; + snapshot_ele->timestamp = delta->timestamp; + snapshot_ele->slot_age = base_slot - fork->slot; + FD_LOG_WARNING(("APPLYING DELTAS %u %lu", delta->pubkey_idx, snapshot_ele->timestamp)); + snapshot_map_ele_insert( snapshot_map, snapshot_ele, snapshot ); + } + } +} + +ulong +fd_vote_timestamps_get_timestamp( fd_vote_timestamps_t * vote_ts, + ushort fork_idx ) { + fork_ele_t * fork_pool = fd_vote_timestamps_get_fork_pool( vote_ts ); + fork_ele_t * fork = fork_pool_ele( fork_pool, fork_idx ); + + ushort path[ USHORT_MAX ]; + ushort path_cnt = 0; + fork->snapshot_idx = prune_and_get_snapshot( vote_ts, fork_idx, path, &path_cnt ); + FD_LOG_NOTICE(("snapshot idx: %u", fork->snapshot_idx)); + snapshot_ele_t * snapshot = fd_vote_timestamps_get_snapshot( vote_ts, fork->snapshot_idx ); + snapshot_map_t * snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_ts, fork->snapshot_idx ); + snapshot_map_reset( snapshot_map ); + + /* We now have the path of all of the vote timestamp entries that we + have to apply. We also have the snapshot index that we can use to + get the timestamp. We want to iterate backwards through the fork + indices and apply the deltas. */ + for( ushort i=0; islot, snapshot, snapshot_map, fork_pool_ele( fork_pool, path[i] ) ); + } + fork_ele_t * curr_fork = fork_pool_ele( fork_pool, path[path_cnt-1] ); + + /* Finally, we need to apply the delta from the previous snapshot */ + snapshot_ele_t * prev_snapshot = fd_vote_timestamps_get_snapshot( vote_ts, curr_fork->snapshot_idx ); + snapshot_map_t * prev_snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_ts, curr_fork->snapshot_idx ); + apply_snapshot( snapshot, snapshot_map, fork->slot, prev_snapshot, prev_snapshot_map, curr_fork->slot ); + + + index_ele_t * index_pool = fd_vote_timestamps_get_index_pool( vote_ts ); + + /* Iterate through the snapshot to get the stake for each pubkey. */ + + ulong ts_ele_cnt = 0UL; + uint128 total_stake = 0UL; + for( snapshot_map_iter_t iter = snapshot_map_iter_init( snapshot_map, snapshot ); + !snapshot_map_iter_done( iter, snapshot_map, snapshot ); + iter = snapshot_map_iter_next( iter, snapshot_map, snapshot ) ) { + uint ele_idx = (uint)snapshot_map_iter_idx( iter, snapshot_map, snapshot ); + snapshot_ele_t * snapshot_ele = snapshot_map_iter_ele( iter, snapshot_map, snapshot ); + index_ele_t * ele = index_pool_ele( index_pool, ele_idx ); + + ulong stake = ele->epoch_stakes[ fork->epoch % 2UL ]; + ulong timestamp = snapshot_ele->timestamp; + ulong slot_delta = snapshot_ele->slot_age; + + if( FD_UNLIKELY( slot_delta>432000UL ) ) { + /* TODO:FIXME: Schedule entry for eviction. */ + } + + /* TODO:FIXME: get the right slot duration on boot */ + ulong offset = fd_ulong_sat_mul( 400e9, slot_delta ); + ulong estimate = timestamp + (offset / ((ulong)1e9)); + FD_LOG_NOTICE(("IDX %u DISTANCE %lu TIMESTAMP %lu ESTIMATE %lu STAKE %lu", ele_idx, slot_delta, timestamp, estimate, stake)); + + vote_ts->ts_eles[ ts_ele_cnt ] = (ts_est_ele_t){ + .timestamp = estimate, + .stake = { .ud=stake }, + }; + ts_ele_cnt++; + + total_stake += stake; + } + + sort_stake_ts_inplace( vote_ts->ts_eles, ts_ele_cnt ); + + /* Populate estimate with the stake-weighted median timestamp. + https://github.com/anza-xyz/agave/blob/v2.3.7/runtime/src/stake_weighted_timestamp.rs#L59-L68 */ + uint128 stake_accumulator = 0; + ulong estimate = 0UL; + for( ulong i=0UL; its_eles[i].stake.ud ); + if( stake_accumulator>(total_stake/2UL) ) { + estimate = vote_ts->ts_eles[ i ].timestamp; + break; + } + } + return estimate; + + /* TODO: Let the runtime handle the timestamp adjusting. */ + +} diff --git a/src/flamenco/stakes/fd_vote_timestamps.h b/src/flamenco/stakes/fd_vote_timestamps.h new file mode 100644 index 00000000000..fb4adc65a4b --- /dev/null +++ b/src/flamenco/stakes/fd_vote_timestamps.h @@ -0,0 +1,80 @@ +#ifndef HEADER_fd_src_flamenco_stakes_fd_vote_timestamps_h +#define HEADER_fd_src_flamenco_stakes_fd_vote_timestamps_h + +#include "../../util/fd_util_base.h" +#include "../../util/tmpl/fd_map.h" +#include "../types/fd_types_custom.h" + +FD_PROTOTYPES_BEGIN + +// STATIC: SIZED FOR 2^25 PUBKEYS MAX, 1.25GiB +// PUBKEYS: Array<(Pubkey, ushort: refcnt)> +// PUBKEY_IDX: uint into pubkeys array +// PUBKEY_MAP: Map + +// attach_child( fork_id: ushort, parent_fork_id: ushort ); +// advance_root( fork_id: ushort ) -> ensure snapshot child +// insert( fork_id: ushort, pubkey: [u8; 32], slot: u64, timestamp: i64 ); +// timestamp( fork_id: ushort ) -> i64; +// +// SNAPSHOTS: 8*2^25*8 bytes in gib ~ 2 GiB +// SNAPSHOTS: Pool> // slot_age 19 bits, timestamp 45 bits +// +// DELTAS: List<(PUBKEY_IDX, timestamp)> // 4096 * 40000 * 4 bytes in gib ~ 0.61 GiB + +struct fd_vote_timestamps; +typedef struct fd_vote_timestamps fd_vote_timestamps_t; + +ulong +fd_vote_timestamps_align( void ); + +ulong +fd_vote_timestamps_footprint( ulong max_live_slots, + uchar max_snaps, + ulong max_vote_accs ); + +void * +fd_vote_timestamps_new( void * shmem, + ulong max_live_slots, + ulong max_snaps, + ulong max_vote_accs, + ulong seed ); + +fd_vote_timestamps_t * +fd_vote_timestamps_join( void * shmem ); + +ushort +fd_vote_timestamps_init( fd_vote_timestamps_t * vote_ts, + ulong slot, + ushort epoch ); + +ushort +fd_vote_timestamps_attach_child( fd_vote_timestamps_t * vote_ts, + ushort parent_fork_idx, + ulong slot, + ushort epoch ); + +void +fd_vote_timestamps_advance_root( fd_vote_timestamps_t * vote_ts, + ushort new_root_idx ); + +void +fd_vote_timestamps_insert( fd_vote_timestamps_t * vote_ts, + ushort fork_idx, + fd_pubkey_t pubkey, + ulong timestamp, + ulong stake ); + +void +fd_vote_timestamps_insert_root( fd_vote_timestamps_t * vote_ts, + fd_pubkey_t pubkey, + ulong timestamp, + ulong stake ); + +ulong +fd_vote_timestamps_get_timestamp( fd_vote_timestamps_t * vote_ts, + ushort fork_idx ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_stakes_fd_vote_timestamps_h */ diff --git a/src/flamenco/stakes/fd_vote_timestamps_private.h b/src/flamenco/stakes/fd_vote_timestamps_private.h new file mode 100644 index 00000000000..989edafc79c --- /dev/null +++ b/src/flamenco/stakes/fd_vote_timestamps_private.h @@ -0,0 +1,169 @@ +#include "../../util/fd_util_base.h" +#include "../types/fd_types_custom.h" + +struct index_ele { + fd_pubkey_t pubkey; + ulong epoch_stakes[ 2UL ]; + ushort refcnt; + uint next; +}; +typedef struct index_ele index_ele_t; + +#define POOL_NAME index_pool +#define POOL_T index_ele_t +#define POOL_NEXT next +#define POOL_IDX_T uint +#include "../../util/tmpl/fd_pool.c" + +#define MAP_NAME index_map +#define MAP_KEY_T fd_pubkey_t +#define MAP_ELE_T index_ele_t +#define MAP_KEY pubkey +#define MAP_KEY_EQ(k0,k1) (fd_pubkey_eq( k0, k1 )) +#define MAP_KEY_HASH(key,seed) (fd_hash( seed, key, sizeof(fd_pubkey_t) )) +#define MAP_NEXT next +#define MAP_IDX_T uint +#include "../../util/tmpl/fd_map_chain.c" + +/**********************************************************************/ + +struct snapshot_ele { + ulong slot_age : 19; + ulong timestamp : 45; + uint idx; + uint next; +}; +typedef struct snapshot_ele snapshot_ele_t; + +#define MAP_NAME snapshot_map +#define MAP_KEY_T uint +#define MAP_ELE_T snapshot_ele_t +#define MAP_KEY idx +#define MAP_NEXT next +#define MAP_IDX_T uint +#include "../../util/tmpl/fd_map_chain.c" + +struct snapshot_key { + ushort fork_idx; + uchar prev; + uchar next; + ulong offset; + ulong map_offset; +}; +typedef struct snapshot_key snapshot_key_ele_t; + +#define DLIST_NAME snapshot_key_dlist +#define DLIST_ELE_T snapshot_key_ele_t +#define DLIST_IDX_T uchar +#include "../../util/tmpl/fd_dlist.c" + +#define POOL_NAME snapshot_key_pool +#define POOL_T snapshot_key_ele_t +#define POOL_IDX_T uchar +#include "../../util/tmpl/fd_pool.c" + +/*********************************************************************/ + +/* TODO:FIXME: this can be improved almost defintely */ + +/* ts_est_ele_t is a temporary struct used for sorting vote accounts by + last vote timestamp for clock sysvar calculation. */ + struct ts_est_ele { + ulong timestamp; + fd_w_u128_t stake; /* should really be fine as ulong, but we match Agave */ + }; + typedef struct ts_est_ele ts_est_ele_t; + +#define SORT_NAME sort_stake_ts +#define SORT_KEY_T ts_est_ele_t +#define SORT_BEFORE(a,b) ( (a).timestamp < (b).timestamp ) +#include "../../util/tmpl/fd_sort.c" + +/* ***************************/ + +struct delta_ele { + ulong timestamp; + uint pubkey_idx; +}; +typedef struct delta_ele delta_ele_t; + +struct fd_vote_timestamps { + ulong fork_pool_offset; + + ulong index_pool_offset; + ulong index_map_offset; + + ushort root_idx; + + ulong snapshot_max; + ulong snapshot_cnt; + ulong snapshot_keys_dlist_offset; + ulong snapshot_keys_pool_offset; + + ts_est_ele_t ts_eles[ 40200 ]; /* TODO:FIXME: this has to be configurable */ +}; +typedef struct fd_vote_timestamps fd_vote_timestamps_t; + +struct fork_ele { + ulong slot; + ushort epoch; + /* left child, right sibling tree pointers */ + ushort parent_idx; + ushort child_idx; + ushort sibling_idx; + ushort next; + + uchar snapshot_idx; + + ushort deltas_cnt; + /* TODO: Const for this or make it paramterizable */ + delta_ele_t deltas[ 42000UL ]; +}; +typedef struct fork_ele fork_ele_t; + +#define POOL_NAME fork_pool +#define POOL_T fork_ele_t +#define POOL_IDX_T ushort +#include "../../util/tmpl/fd_pool.c" + +static inline fork_ele_t * +fd_vote_timestamps_get_fork_pool( fd_vote_timestamps_t * vote_ts ) { + return fd_type_pun( (uchar *)vote_ts + vote_ts->fork_pool_offset ); +} + +static inline index_ele_t * +fd_vote_timestamps_get_index_pool( fd_vote_timestamps_t * vote_ts ) { + return fd_type_pun( (uchar *)vote_ts + vote_ts->index_pool_offset ); +} + +static inline index_map_t * +fd_vote_timestamps_get_index_map( fd_vote_timestamps_t * vote_ts ) { + return fd_type_pun( (uchar *)vote_ts + vote_ts->index_map_offset ); +} + +static inline snapshot_key_dlist_t * +fd_vote_timestamps_get_snapshot_keys_dlist( fd_vote_timestamps_t * vote_ts ) { + return fd_type_pun( (uchar *)vote_ts + vote_ts->snapshot_keys_dlist_offset ); +} + +static inline snapshot_key_ele_t * +fd_vote_timestamps_get_snapshot_keys_pool( fd_vote_timestamps_t * vote_ts ) { + return fd_type_pun( (uchar *)vote_ts + vote_ts->snapshot_keys_pool_offset ); +} + +static inline snapshot_ele_t * +fd_vote_timestamps_get_snapshot( fd_vote_timestamps_t * vote_ts, + uchar snapshot_idx ) { + /* TODO:FIXME: this is pretty hacky. */ + snapshot_key_ele_t * snapshot_keys_pool = fd_vote_timestamps_get_snapshot_keys_pool( vote_ts ); + snapshot_key_ele_t * key = snapshot_key_pool_ele( snapshot_keys_pool, snapshot_idx ); + return fd_type_pun( (uchar *)vote_ts + key->offset ); +} + +static inline snapshot_map_t * +fd_vote_timestamps_get_snapshot_map( fd_vote_timestamps_t * vote_ts, + uchar snapshot_idx ) { + snapshot_key_ele_t * snapshot_keys_pool = fd_vote_timestamps_get_snapshot_keys_pool( vote_ts ); + snapshot_key_ele_t * key = snapshot_key_pool_ele( snapshot_keys_pool, snapshot_idx ); + return fd_type_pun( (uchar *)vote_ts + key->map_offset ); +} diff --git a/src/flamenco/stakes/test_vote_timestamps.c b/src/flamenco/stakes/test_vote_timestamps.c new file mode 100644 index 00000000000..73f36f233e1 --- /dev/null +++ b/src/flamenco/stakes/test_vote_timestamps.c @@ -0,0 +1,290 @@ +#include "fd_vote_timestamps.h" +#include "fd_vote_timestamps_private.h" + +int main( int argc, char ** argv ) { + fd_boot( &argc, &argv ); + + char const * name = fd_env_strip_cmdline_cstr ( &argc, &argv, "--wksp", NULL, NULL ); + char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); + ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1UL ); + ulong near_cpu = fd_env_strip_cmdline_ulong( &argc, &argv, "--near-cpu", NULL, fd_log_cpu_id() ); + ulong wksp_tag = fd_env_strip_cmdline_ulong( &argc, &argv, "--wksp-tag", NULL, 1234UL ); + + fd_pubkey_t pubkey_A = {.ul = {0}}; + fd_pubkey_t pubkey_B = {.ul = {1}}; + fd_pubkey_t pubkey_C = {.ul = {2}}; + fd_pubkey_t pubkey_D = {.ul = {3}}; + fd_pubkey_t pubkey_E = {.ul = {4}}; + fd_pubkey_t pubkey_F = {.ul = {5}}; (void)pubkey_F; + fd_pubkey_t pubkey_G = {.ul = {6}}; (void)pubkey_G; + fd_pubkey_t pubkey_H = {.ul = {7}}; (void)pubkey_H; + + fd_wksp_t * wksp; + if( name ) { + FD_LOG_NOTICE(( "Attaching to --wksp %s", name )); + wksp = fd_wksp_attach( name ); + } else { + FD_LOG_NOTICE(( "--wksp not specified, using an anonymous local workspace, --page-sz %s, --page-cnt %lu, --near-cpu %lu", + _page_sz, page_cnt, near_cpu )); + wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), page_cnt, near_cpu, "wksp", 0UL ); + } + + ulong footprint = fd_vote_timestamps_footprint( 16UL, 4, 128UL ); + FD_LOG_NOTICE(( "footprint: %lu", footprint )); + + uchar * mem = fd_wksp_alloc_laddr( wksp, fd_vote_timestamps_align(), footprint, wksp_tag ); + FD_TEST( mem ); + + + fd_vote_timestamps_t * vote_timestamps = fd_vote_timestamps_join( fd_vote_timestamps_new( mem, 16UL, 4, 128UL, 0UL ) ); + FD_TEST( vote_timestamps ); + + fork_ele_t * fork_pool = fd_vote_timestamps_get_fork_pool( vote_timestamps ); + + ushort root_idx = fd_vote_timestamps_init( vote_timestamps, 0UL, 0 ); + FD_TEST( root_idx==0 ); + + fd_vote_timestamps_insert_root( vote_timestamps, pubkey_A, 10, 100UL ); + fd_vote_timestamps_insert_root( vote_timestamps, pubkey_B, 10, 200UL ); + fd_vote_timestamps_insert_root( vote_timestamps, pubkey_C, 10, 300UL ); + fd_vote_timestamps_insert_root( vote_timestamps, pubkey_D, 10, 400UL ); + fd_vote_timestamps_insert_root( vote_timestamps, pubkey_E, 10, 500UL ); + + FD_TEST( 5U==index_pool_used(fd_vote_timestamps_get_index_pool( vote_timestamps ) ) ); + fork_ele_t * root = fork_pool_ele( fork_pool, vote_timestamps->root_idx ); + FD_TEST( root->deltas_cnt==0 ); + FD_TEST( root->snapshot_idx!=UCHAR_MAX ); + FD_TEST( root->parent_idx==USHORT_MAX ); + FD_TEST( root->child_idx==USHORT_MAX ); + FD_TEST( root->sibling_idx==USHORT_MAX ); + + ushort child_idx = fd_vote_timestamps_attach_child( vote_timestamps, root_idx, 1UL, 0 ); + fd_vote_timestamps_insert( vote_timestamps, child_idx, pubkey_A, 11, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx, pubkey_B, 11, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx, pubkey_C, 11, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx, pubkey_D, 11, 0UL ); + FD_TEST( 5U==index_pool_used(fd_vote_timestamps_get_index_pool( vote_timestamps ) ) ); + fork_ele_t * child_fork = fork_pool_ele( fork_pool, child_idx ); + FD_TEST( child_fork->deltas_cnt==4 ); + FD_TEST( root->child_idx==child_idx ); + FD_TEST( child_fork->parent_idx==root_idx ); + FD_TEST( child_fork->child_idx==USHORT_MAX ); + FD_TEST( child_fork->sibling_idx==USHORT_MAX ); + FD_TEST( root->snapshot_idx!=UCHAR_MAX ); + fd_vote_timestamps_get_timestamp( vote_timestamps, child_idx ); + snapshot_ele_t * snapshot = fd_vote_timestamps_get_snapshot( vote_timestamps, child_fork->snapshot_idx ); + snapshot_map_t * snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_timestamps, child_fork->snapshot_idx ); + + ulong t_10_cnt = 0UL; + ulong t_11_cnt = 0UL; + for( snapshot_map_iter_t iter = snapshot_map_iter_init( snapshot_map, snapshot ); + !snapshot_map_iter_done( iter, snapshot_map, snapshot ); + iter = snapshot_map_iter_next( iter, snapshot_map, snapshot ) ) { + snapshot_ele_t * ele = snapshot_map_iter_ele( iter, snapshot_map, snapshot ); + if( ele->timestamp==10 ) t_10_cnt++; + else if( ele->timestamp==11 ) t_11_cnt++; + else FD_TEST( 0 ); + } + FD_TEST( t_10_cnt==1 ); + FD_TEST( t_11_cnt==4 ); + + + ushort child_idx2 = fd_vote_timestamps_attach_child( vote_timestamps, child_idx, 2UL, 0 ); + fd_vote_timestamps_insert( vote_timestamps, child_idx2, pubkey_F, 11, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx2, pubkey_A, 15, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx2, pubkey_B, 15, 0UL ); + FD_TEST( 6U==index_pool_used(fd_vote_timestamps_get_index_pool( vote_timestamps ) ) ); + fd_vote_timestamps_get_timestamp( vote_timestamps, child_idx2 ); + fork_ele_t * child_fork2 = fork_pool_ele( fork_pool, child_idx2 ); + FD_TEST( child_fork->child_idx==child_idx2 ); + FD_TEST( child_fork2->parent_idx==child_idx ); + FD_TEST( child_fork2->child_idx==USHORT_MAX ); + FD_TEST( child_fork2->sibling_idx==USHORT_MAX ); + FD_TEST( child_fork2->deltas_cnt==3 ); + FD_TEST( root->snapshot_idx!=UCHAR_MAX ); + snapshot = fd_vote_timestamps_get_snapshot( vote_timestamps, child_fork2->snapshot_idx ); + snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_timestamps, child_fork2->snapshot_idx ); + t_10_cnt = 0UL; + t_11_cnt = 0UL; + ulong t_15_cnt = 0UL; + for( snapshot_map_iter_t iter = snapshot_map_iter_init( snapshot_map, snapshot ); + !snapshot_map_iter_done( iter, snapshot_map, snapshot ); + iter = snapshot_map_iter_next( iter, snapshot_map, snapshot ) ) { + snapshot_ele_t * ele = snapshot_map_iter_ele( iter, snapshot_map, snapshot ); + if( ele->timestamp==10 ) t_10_cnt++; + else if( ele->timestamp==11 ) t_11_cnt++; + else if( ele->timestamp==15 ) t_15_cnt++; + else FD_TEST( 0 ); + } + FD_TEST( t_10_cnt==1 ); + FD_TEST( t_11_cnt==3 ); + FD_TEST( t_15_cnt==2 ); + + ushort child_idx3 = fd_vote_timestamps_attach_child( vote_timestamps, child_idx, 3UL, 0 ); + fd_vote_timestamps_insert( vote_timestamps, child_idx3, pubkey_F, 11, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx3, pubkey_A, 15, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx3, pubkey_B, 15, 0UL ); + FD_TEST( 6U==index_pool_used(fd_vote_timestamps_get_index_pool( vote_timestamps ) ) ); + fd_vote_timestamps_get_timestamp( vote_timestamps, child_idx3 ); + fork_ele_t * child_fork3 = fork_pool_ele( fork_pool, child_idx3 ); + FD_TEST( child_fork->child_idx==child_idx2 ); + FD_TEST( child_fork2->sibling_idx==child_idx3 ); + FD_TEST( child_fork3->parent_idx==child_idx ); + FD_TEST( child_fork3->child_idx==USHORT_MAX ); + FD_TEST( child_fork3->sibling_idx==USHORT_MAX ); + FD_TEST( child_fork3->deltas_cnt==3 ); + FD_TEST( root->snapshot_idx!=UCHAR_MAX ); + snapshot = fd_vote_timestamps_get_snapshot( vote_timestamps, child_fork3->snapshot_idx ); + snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_timestamps, child_fork3->snapshot_idx ); + t_10_cnt = 0UL; + t_11_cnt = 0UL; + t_15_cnt = 0UL; + for( snapshot_map_iter_t iter = snapshot_map_iter_init( snapshot_map, snapshot ); + !snapshot_map_iter_done( iter, snapshot_map, snapshot ); + iter = snapshot_map_iter_next( iter, snapshot_map, snapshot ) ) { + snapshot_ele_t * ele = snapshot_map_iter_ele( iter, snapshot_map, snapshot ); + if( ele->timestamp==10 ) t_10_cnt++; + else if( ele->timestamp==11 ) t_11_cnt++; + else if( ele->timestamp==15 ) t_15_cnt++; + else FD_TEST( 0 ); + } + FD_TEST( t_10_cnt==1 ); + FD_TEST( t_11_cnt==3 ); + FD_TEST( t_15_cnt==2 ); + + /* Make sure the eviction policy is working. At this point we expect + LRU eviction to kick in excluding the root and the best option. + In this case the best option is child_idx_2, so we expect child_idx + to have its snapshot evicted. */ + ushort child_idx4 = fd_vote_timestamps_attach_child( vote_timestamps, child_idx2, 4UL, 0 ); + fd_vote_timestamps_insert( vote_timestamps, child_idx4, pubkey_C, 16, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx4, pubkey_A, 15, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx4, pubkey_B, 15, 0UL ); + fd_vote_timestamps_get_timestamp( vote_timestamps, child_idx4 ); + fork_ele_t * child_fork4 = fork_pool_ele( fork_pool, child_idx4 ); + FD_TEST( child_fork4->parent_idx==child_idx2 ); + FD_TEST( child_fork4->snapshot_idx!=UCHAR_MAX ); + FD_TEST( child_fork->snapshot_idx==UCHAR_MAX ); + FD_TEST( child_fork4->deltas_cnt==3 ); + FD_TEST( root->snapshot_idx!=UCHAR_MAX ); + t_10_cnt = 0UL; + t_11_cnt = 0UL; + t_15_cnt = 0UL; + ulong t_16_cnt = 0UL; + snapshot = fd_vote_timestamps_get_snapshot( vote_timestamps, child_fork4->snapshot_idx ); + snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_timestamps, child_fork4->snapshot_idx ); + for( snapshot_map_iter_t iter = snapshot_map_iter_init( snapshot_map, snapshot ); + !snapshot_map_iter_done( iter, snapshot_map, snapshot ); + iter = snapshot_map_iter_next( iter, snapshot_map, snapshot ) ) { + snapshot_ele_t * ele = snapshot_map_iter_ele( iter, snapshot_map, snapshot ); + if( ele->timestamp==10 ) t_10_cnt++; + else if( ele->timestamp==11 ) t_11_cnt++; + else if( ele->timestamp==15 ) t_15_cnt++; + else if( ele->timestamp==16 ) t_16_cnt++; + else FD_TEST( 0 ); + } + FD_TEST( t_10_cnt==1 ); + FD_TEST( t_11_cnt==2 ); + FD_TEST( t_15_cnt==2 ); + FD_TEST( t_16_cnt==1 ); + + /* Now try to make a child off of child_idx and see if the skipped + delta gets applied correctly. We also should expect to see + child_idx2's snapshot to be evicted. Make sure that the root's + snapshot idx does not get evicted. */ + + ushort child_idx5 = fd_vote_timestamps_attach_child( vote_timestamps, child_idx, 5UL, 0 ); + fd_vote_timestamps_insert( vote_timestamps, child_idx5, pubkey_A, 20, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx5, pubkey_B, 20, 0UL ); + fd_vote_timestamps_insert( vote_timestamps, child_idx5, pubkey_C, 20, 0UL ); + fd_vote_timestamps_get_timestamp( vote_timestamps, child_idx5 ); + + fork_ele_t * child_fork5 = fork_pool_ele( fork_pool, child_idx5 ); + FD_TEST( child_fork2->snapshot_idx==UCHAR_MAX ); + FD_TEST( root->snapshot_idx!=UCHAR_MAX ); + FD_TEST( child_fork5->snapshot_idx!=UCHAR_MAX ); + + ulong t_20_cnt = 0UL; + t_11_cnt = 0UL; + t_10_cnt = 0UL; + snapshot = fd_vote_timestamps_get_snapshot( vote_timestamps, child_fork5->snapshot_idx ); + snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_timestamps, child_fork5->snapshot_idx ); + for( snapshot_map_iter_t iter = snapshot_map_iter_init( snapshot_map, snapshot ); + !snapshot_map_iter_done( iter, snapshot_map, snapshot ); + iter = snapshot_map_iter_next( iter, snapshot_map, snapshot ) ) { + snapshot_ele_t * ele = snapshot_map_iter_ele( iter, snapshot_map, snapshot ); + if( ele->timestamp==20 ) t_20_cnt++; + else if( ele->timestamp==11 ) t_11_cnt++; + else if( ele->timestamp==10 ) t_10_cnt++; + else FD_TEST( 0 ); + } + FD_TEST( t_20_cnt==3 ); + FD_TEST( t_11_cnt==1 ); + FD_TEST( t_10_cnt==1 ); + + ushort child_idx6 = fd_vote_timestamps_attach_child( vote_timestamps, child_idx2, 6UL, 0 ); + fd_vote_timestamps_get_timestamp( vote_timestamps, child_idx6 ); + FD_TEST( child_fork3->snapshot_idx==UCHAR_MAX ); + FD_TEST( root->snapshot_idx!=UCHAR_MAX ); + + /* Advance the root to a node that does not have a snapshot: in this + case move to child_idx. This node also has no siblings so only + the old root will get pruned. */ + fd_vote_timestamps_advance_root( vote_timestamps, child_idx ); + /* TODO: Asserts here. Make sure that the values in the snapshot are + what we expect them to be. Also validate the fork structure at + this point. */ + fork_ele_t * new_root = fork_pool_ele( fork_pool, vote_timestamps->root_idx ); + FD_TEST( new_root->deltas_cnt==0 ); + FD_TEST( new_root->snapshot_idx!=UCHAR_MAX ); + snapshot = fd_vote_timestamps_get_snapshot( vote_timestamps, new_root->snapshot_idx ); + snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_timestamps, new_root->snapshot_idx ); + ulong ts_10_cnt = 0UL; + ulong ts_11_cnt = 0UL; + for( snapshot_map_iter_t iter = snapshot_map_iter_init( snapshot_map, snapshot ); + !snapshot_map_iter_done( iter, snapshot_map, snapshot ); + iter = snapshot_map_iter_next( iter, snapshot_map, snapshot ) ) { + snapshot_ele_t * ele = snapshot_map_iter_ele( iter, snapshot_map, snapshot ); + FD_TEST( ele->timestamp==10 || ele->timestamp==11 ); + if( ele->timestamp==10 ) ts_10_cnt++; + else ts_11_cnt++; + } + FD_TEST( ts_10_cnt==1 ); + FD_TEST( ts_11_cnt==4 ); + + /* Now try advancing the root to the child_idx5 which has a snapshot. + Also now every other element in the tree will be pruned. */ + fd_vote_timestamps_advance_root( vote_timestamps, child_idx5 ); + new_root = fork_pool_ele( fork_pool, vote_timestamps->root_idx ); + FD_TEST( new_root->deltas_cnt==0 ); + FD_TEST( new_root->snapshot_idx!=UCHAR_MAX ); + FD_TEST( fork_pool_used( fork_pool )==1 ); + + snapshot_key_ele_t * snapshot_keys_pool = fd_vote_timestamps_get_snapshot_keys_pool( vote_timestamps ); + FD_TEST( snapshot_key_pool_used( snapshot_keys_pool )==1 ); + /* Make sure pubkey_F is pruned from the index. */ + FD_TEST( 5U==index_pool_used(fd_vote_timestamps_get_index_pool( vote_timestamps ) ) ); + snapshot = fd_vote_timestamps_get_snapshot( vote_timestamps, new_root->snapshot_idx ); + snapshot_map = fd_vote_timestamps_get_snapshot_map( vote_timestamps, new_root->snapshot_idx ); + + t_20_cnt = 0UL; + t_11_cnt = 0UL; + t_10_cnt = 0UL; + for( snapshot_map_iter_t iter = snapshot_map_iter_init( snapshot_map, snapshot ); + !snapshot_map_iter_done( iter, snapshot_map, snapshot ); + iter = snapshot_map_iter_next( iter, snapshot_map, snapshot ) ) { + snapshot_ele_t * ele = snapshot_map_iter_ele( iter, snapshot_map, snapshot ); + if( ele->timestamp==20 ) t_20_cnt++; + else if( ele->timestamp==11 ) t_11_cnt++; + else if( ele->timestamp==10 ) t_10_cnt++; + else FD_TEST( 0 ); + } + FD_TEST( t_20_cnt==3 ); + FD_TEST( t_11_cnt==1 ); + FD_TEST( t_10_cnt==1 ); + + + /* TODO: The index currently leaks elements. */ + + FD_LOG_NOTICE(( "pass" )); +}