diff --git a/.gitignore b/.gitignore index 2c062b6..d170f5e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Build artifacts build/ +build* cmake-build-*/ # Prerequisites diff --git a/include/lantern/consensus/fork_choice.h b/include/lantern/consensus/fork_choice.h index 0a688c2..6baa5a1 100644 --- a/include/lantern/consensus/fork_choice.h +++ b/include/lantern/consensus/fork_choice.h @@ -50,6 +50,8 @@ struct lantern_fork_choice_root_index_entry { typedef struct lantern_fork_choice { bool initialized; bool has_anchor; + LanternRoot anchor_root; + uint64_t anchor_slot; LanternConfig config; uint32_t seconds_per_slot; uint32_t intervals_per_slot; @@ -164,6 +166,7 @@ int lantern_fork_choice_restore_checkpoints( LanternForkChoice *store, const LanternCheckpoint *latest_justified, const LanternCheckpoint *latest_finalized); +int lantern_fork_choice_prune_states(LanternForkChoice *store); int lantern_fork_choice_accept_new_votes(LanternForkChoice *store); int lantern_fork_choice_update_safe_target(LanternForkChoice *store); @@ -192,6 +195,8 @@ int lantern_fork_choice_set_block_state( const LanternState *lantern_fork_choice_block_state( const LanternForkChoice *store, const LanternRoot *root); +const LanternRoot *lantern_fork_choice_anchor_root(const LanternForkChoice *store); +int lantern_fork_choice_anchor_slot(const LanternForkChoice *store, uint64_t *out_slot); const LanternCheckpoint *lantern_fork_choice_latest_justified(const LanternForkChoice *store); const LanternCheckpoint *lantern_fork_choice_latest_finalized(const LanternForkChoice *store); const LanternRoot *lantern_fork_choice_safe_target(const LanternForkChoice *store); diff --git a/src/consensus/fork_choice.c b/src/consensus/fork_choice.c index d083ff7..b3f46db 100644 --- a/src/consensus/fork_choice.c +++ b/src/consensus/fork_choice.c @@ -455,6 +455,8 @@ void lantern_fork_choice_reset(LanternForkChoice *store) { store->initialized = false; store->has_anchor = false; + zero_root(&store->anchor_root); + store->anchor_slot = 0; store->has_head = false; store->has_safe_target = false; zero_root(&store->head); @@ -636,6 +638,21 @@ const LanternState *lantern_fork_choice_block_state( return entry->has_state ? &entry->state : NULL; } +const LanternRoot *lantern_fork_choice_anchor_root(const LanternForkChoice *store) { + if (!store || !store->has_anchor) { + return NULL; + } + return &store->anchor_root; +} + +int lantern_fork_choice_anchor_slot(const LanternForkChoice *store, uint64_t *out_slot) { + if (!store || !store->has_anchor || !out_slot) { + return -1; + } + *out_slot = store->anchor_slot; + return 0; +} + int lantern_fork_choice_set_anchor( LanternForkChoice *store, const LanternBlock *anchor_block, @@ -682,6 +699,8 @@ int lantern_fork_choice_set_anchor_with_state( size_t previous_block_len = store->block_len; LanternCheckpoint previous_justified = store->latest_justified; LanternCheckpoint previous_finalized = store->latest_finalized; + LanternRoot previous_anchor_root = store->anchor_root; + uint64_t previous_anchor_slot = store->anchor_slot; LanternRoot previous_head = store->head; bool previous_has_head = store->has_head; LanternRoot previous_safe_target = store->safe_target; @@ -715,11 +734,15 @@ int lantern_fork_choice_set_anchor_with_state( store->safe_target = root; store->has_safe_target = true; store->has_anchor = true; + store->anchor_root = root; + store->anchor_slot = anchor_block->slot; uint64_t anchor_intervals = anchor_block->slot * store->intervals_per_slot; store->time_intervals = anchor_intervals; if (anchor_state && lantern_fork_choice_set_block_state(store, &root, anchor_state) != 0) { store->latest_justified = previous_justified; store->latest_finalized = previous_finalized; + store->anchor_root = previous_anchor_root; + store->anchor_slot = previous_anchor_slot; store->head = previous_head; store->has_head = previous_has_head; store->safe_target = previous_safe_target; @@ -1070,6 +1093,68 @@ int lantern_fork_choice_restore_checkpoints( return 0; } +int lantern_fork_choice_prune_states(LanternForkChoice *store) { + if (!store || !store->initialized || !store->has_anchor || !store->has_head) { + return -1; + } + if (store->block_len == 0 || !store->states) { + return 0; + } + if (store->state_cap < store->block_len) { + return -1; + } + if (root_is_zero(&store->latest_finalized.root)) { + return 0; + } + + size_t head_index = 0; + size_t finalized_index = 0; + if (!map_lookup(store, &store->head, &head_index) + || !map_lookup(store, &store->latest_finalized.root, &finalized_index)) { + return -1; + } + if (head_index >= store->block_len || finalized_index >= store->block_len) { + return -1; + } + + uint8_t *canonical = calloc(store->block_len, sizeof(*canonical)); + if (!canonical) { + return -1; + } + + bool found_finalized = false; + size_t current = head_index; + while (current < store->block_len) { + canonical[current] = 1u; + if (current == finalized_index) { + found_finalized = true; + break; + } + size_t parent_index = store->blocks[current].parent_index; + if (parent_index == SIZE_MAX || parent_index >= store->block_len) { + break; + } + current = parent_index; + } + + if (!found_finalized) { + free(canonical); + return -1; + } + + for (size_t i = 0; i < store->block_len; ++i) { + struct lantern_fork_choice_state_entry *entry = &store->states[i]; + if (!entry->has_state || canonical[i] != 0u) { + continue; + } + lantern_state_reset(&entry->state); + entry->has_state = false; + } + + free(canonical); + return 0; +} + static int find_start_index( const LanternForkChoice *store, const LanternRoot *start_root, diff --git a/src/core/client_sync.c b/src/core/client_sync.c index 76acfa5..57c2827 100644 --- a/src/core/client_sync.c +++ b/src/core/client_sync.c @@ -1996,6 +1996,78 @@ static int pending_child_replay_compare(const void *left, const void *right) return memcmp(left_entry->root.bytes, right_entry->root.bytes, LANTERN_ROOT_SIZE); } +struct pending_child_root_queue +{ + LanternRoot *items; + size_t length; + size_t capacity; + size_t next_index; +}; + +static void pending_child_root_queue_init(struct pending_child_root_queue *queue) +{ + if (!queue) + { + return; + } + memset(queue, 0, sizeof(*queue)); +} + +static void pending_child_root_queue_reset(struct pending_child_root_queue *queue) +{ + if (!queue) + { + return; + } + free(queue->items); + memset(queue, 0, sizeof(*queue)); +} + +static bool pending_child_root_queue_append( + struct pending_child_root_queue *queue, + const LanternRoot *root) +{ + if (!queue || !root) + { + return false; + } + if (queue->length == queue->capacity) + { + size_t next_capacity = queue->capacity == 0 ? 8u : (queue->capacity + (queue->capacity / 2u)); + if (next_capacity < queue->capacity) + { + return false; + } + if (next_capacity > SIZE_MAX / sizeof(*queue->items)) + { + return false; + } + LanternRoot *expanded = realloc(queue->items, next_capacity * sizeof(*expanded)); + if (!expanded) + { + return false; + } + queue->items = expanded; + queue->capacity = next_capacity; + } + queue->items[queue->length] = *root; + queue->length += 1u; + return true; +} + +static bool pending_child_root_queue_next( + struct pending_child_root_queue *queue, + LanternRoot *out_root) +{ + if (!queue || !out_root || queue->next_index >= queue->length) + { + return false; + } + *out_root = queue->items[queue->next_index]; + queue->next_index += 1u; + return true; +} + void lantern_client_request_pending_parent_after_blocks( struct lantern_client *client, const char *peer_text, @@ -2320,6 +2392,7 @@ void lantern_client_enqueue_pending_block( existing->received_ms = monotonic_millis(); size_t pending_len = list->length; bool parent_requested = existing->parent_requested; + uint32_t existing_backfill_depth = existing->backfill_depth; lantern_client_unlock_pending(client, locked); char root_hex[ROOT_HEX_BUFFER_LEN]; char parent_hex[ROOT_HEX_BUFFER_LEN]; @@ -2334,15 +2407,15 @@ void lantern_client_enqueue_pending_block( "parent_requested=%s should_request=%s", root_hex[0] ? root_hex : "0x0", parent_hex[0] ? parent_hex : "0x0", - existing->backfill_depth, + existing_backfill_depth, pending_len, parent_cached ? "true" : "false", parent_requested ? "true" : "false", should_request ? "true" : "false"); if (should_request && !parent_cached - && existing->backfill_depth < LANTERN_MAX_BACKFILL_DEPTH) + && existing_backfill_depth < LANTERN_MAX_BACKFILL_DEPTH) { - uint32_t request_depth = existing->backfill_depth + 1u; + uint32_t request_depth = existing_backfill_depth + 1u; if (try_schedule_blocks_request( client, peer_copy[0] ? peer_copy : NULL, @@ -2442,6 +2515,7 @@ void lantern_client_enqueue_pending_block( entry->parent_requested = false; bool parent_cached = pending_block_list_find(list, &parent_root_local) != NULL; size_t pending_len = list->length; + uint32_t entry_backfill_depth = entry->backfill_depth; lantern_client_unlock_pending(client, locked); @@ -2467,7 +2541,7 @@ void lantern_client_enqueue_pending_block( } if (allow_parent_requests && request_parent_now && !parent_cached - && entry->backfill_depth < LANTERN_MAX_BACKFILL_DEPTH) + && entry_backfill_depth < LANTERN_MAX_BACKFILL_DEPTH) { char peer_copy[PEER_TEXT_BUFFER_LEN]; peer_copy[0] = '\0'; @@ -2476,7 +2550,7 @@ void lantern_client_enqueue_pending_block( strncpy(peer_copy, peer_text, sizeof(peer_copy) - 1u); peer_copy[sizeof(peer_copy) - 1u] = '\0'; } - uint32_t request_depth = entry->backfill_depth + 1u; + uint32_t request_depth = entry_backfill_depth + 1u; if (try_schedule_blocks_request( client, peer_copy[0] ? peer_copy : NULL, @@ -2494,7 +2568,7 @@ void lantern_client_enqueue_pending_block( "request_parent=%s", block_hex[0] ? block_hex : "0x0", parent_hex[0] ? parent_hex : "0x0", - entry->backfill_depth, + entry_backfill_depth, pending_len, parent_cached ? "true" : "false", (allow_parent_requests && request_parent_now) ? "true" : "false"); @@ -2508,7 +2582,7 @@ void lantern_client_enqueue_pending_block( * * After importing a block, checks if any pending blocks can now * be imported (because their parent just became available). - * Recursively processes any chains of pending blocks. + * Iteratively processes any chains of pending blocks. * * @param client Client instance * @param parent_root Root of the newly imported parent block @@ -2523,19 +2597,28 @@ void lantern_client_process_pending_children( { return; } - while (true) + struct pending_child_root_queue parent_queue; + pending_child_root_queue_init(&parent_queue); + if (!pending_child_root_queue_append(&parent_queue, parent_root)) + { + pending_child_root_queue_reset(&parent_queue); + return; + } + + LanternRoot current_parent = {0}; + while (pending_child_root_queue_next(&parent_queue, ¤t_parent)) { bool locked = lantern_client_lock_pending(client); if (!locked) { - return; + break; } size_t pending_count = 0; for (size_t i = 0; i < client->pending_blocks.length; ++i) { struct lantern_pending_block *entry = &client->pending_blocks.items[i]; - if (memcmp(entry->parent_root.bytes, parent_root->bytes, LANTERN_ROOT_SIZE) == 0) + if (memcmp(entry->parent_root.bytes, current_parent.bytes, LANTERN_ROOT_SIZE) == 0) { pending_count += 1u; } @@ -2544,7 +2627,7 @@ void lantern_client_process_pending_children( if (pending_count == 0) { lantern_client_unlock_pending(client, locked); - break; + continue; } struct pending_child_replay *replays = @@ -2552,14 +2635,14 @@ void lantern_client_process_pending_children( if (!replays) { lantern_client_unlock_pending(client, locked); - return; + break; } size_t replay_count = 0; for (size_t i = client->pending_blocks.length; i-- > 0;) { struct lantern_pending_block *entry = &client->pending_blocks.items[i]; - if (memcmp(entry->parent_root.bytes, parent_root->bytes, LANTERN_ROOT_SIZE) != 0) + if (memcmp(entry->parent_root.bytes, current_parent.bytes, LANTERN_ROOT_SIZE) != 0) { continue; } @@ -2600,13 +2683,15 @@ void lantern_client_process_pending_children( qsort(replays, replay_count, sizeof(*replays), pending_child_replay_compare); size_t imported_count = 0; + size_t queued_children = 0; for (size_t i = 0; i < replay_count; ++i) { struct lantern_log_metadata meta = { .validator = client->node_id, .peer = replays[i].peer_text[0] ? replays[i].peer_text : NULL, }; - bool imported = lantern_client_import_block( + bool children_ready = false; + bool imported = lantern_client_import_block_without_pending_children( client, &replays[i].block, &replays[i].root, @@ -2614,29 +2699,45 @@ void lantern_client_process_pending_children( replays[i].backfill_depth, true, NULL, - 0); + 0, + &children_ready); if (imported) { imported_count += 1u; } + if (children_ready) + { + if (pending_child_root_queue_append(&parent_queue, &replays[i].root)) + { + queued_children += 1u; + } + else + { + char root_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(&replays[i].root, root_hex, sizeof(root_hex)); + lantern_log_warn( + "sync", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "failed to queue pending child root=%s for iterative replay", + root_hex[0] ? root_hex : "0x0"); + } + } lantern_signed_block_with_attestation_reset(&replays[i].block); } free(replays); char parent_hex[ROOT_HEX_BUFFER_LEN]; - format_root_hex(parent_root, parent_hex, sizeof(parent_hex)); + format_root_hex(¤t_parent, parent_hex, sizeof(parent_hex)); lantern_log_debug( "sync", &(const struct lantern_log_metadata){.validator = client->node_id}, - "pending children processed parent=%s pending=%zu replayed=%zu imported=%zu", + "pending children processed parent=%s pending=%zu replayed=%zu imported=%zu queued=%zu", parent_hex[0] ? parent_hex : "0x0", pending_count, replay_count, - imported_count); - - if (imported_count == 0) - { - break; - } + imported_count, + queued_children); } + + pending_child_root_queue_reset(&parent_queue); } diff --git a/src/core/client_sync_blocks.c b/src/core/client_sync_blocks.c index 841b3ef..f32c5dd 100644 --- a/src/core/client_sync_blocks.c +++ b/src/core/client_sync_blocks.c @@ -765,6 +765,8 @@ static bool build_root_chain_locked( struct lantern_client *client, const LanternRoot *target_root, const LanternRoot *stop_root, + bool allow_stop_at_or_before_slot, + uint64_t stop_slot, struct lantern_root_chain *out_chain) { if (!client || !target_root || !out_chain || !client->has_fork_choice) @@ -775,16 +777,10 @@ static bool build_root_chain_locked( LanternRoot current = *target_root; const bool has_stop_root = stop_root && !lantern_root_is_zero(stop_root); - bool reached_stop_root = false; + bool reached_stop_boundary = false; size_t steps = 0; while (!lantern_root_is_zero(¤t)) { - if (has_stop_root - && memcmp(current.bytes, stop_root->bytes, LANTERN_ROOT_SIZE) == 0) - { - reached_stop_root = true; - break; - } if (steps > LANTERN_HISTORICAL_ROOTS_LIMIT) { return false; @@ -808,6 +804,19 @@ static bool build_root_chain_locked( return false; } + if (has_stop_root + && memcmp(current.bytes, stop_root->bytes, LANTERN_ROOT_SIZE) == 0) + { + reached_stop_boundary = true; + break; + } + + if (allow_stop_at_or_before_slot && slot <= stop_slot) + { + reached_stop_boundary = true; + break; + } + if (!root_chain_append(out_chain, ¤t, &parent, slot, has_parent)) { return false; @@ -822,9 +831,9 @@ static bool build_root_chain_locked( steps += 1u; } - if (has_stop_root) + if (has_stop_root || allow_stop_at_or_before_slot) { - return reached_stop_root; + return reached_stop_boundary; } return out_chain->length > 0; @@ -1542,6 +1551,9 @@ static bool rebuild_state_for_root_locked( bool have_replay_base_state = false; bool use_finalized_shortcut = false; LanternRoot replay_stop_root = {0}; + uint64_t replay_stop_slot = 0; + uint64_t anchor_slot = 0; + bool allow_anchor_slot_stop = false; char replay_stop_hex[ROOT_HEX_BUFFER_LEN] = {0}; if (client->has_fork_choice) @@ -1551,14 +1563,36 @@ static bool rebuild_state_for_root_locked( if (fork_finalized) { replay_stop_root = fork_finalized->root; + replay_stop_slot = fork_finalized->slot; } } else if (client->has_state) { replay_stop_root = client->state.latest_finalized.root; + replay_stop_slot = client->state.latest_finalized.slot; } if (!lantern_root_is_zero(&replay_stop_root)) { + if (client->has_fork_choice) + { + const LanternRoot *anchor_root = + lantern_fork_choice_anchor_root(&client->fork_choice); + if (anchor_root + && memcmp( + anchor_root->bytes, + replay_stop_root.bytes, + LANTERN_ROOT_SIZE) + == 0 + && lantern_fork_choice_anchor_slot( + &client->fork_choice, + &anchor_slot) + == 0 + && replay_stop_slot < anchor_slot) + { + allow_anchor_slot_stop = true; + } + } + const char *replay_base_source = NULL; size_t replay_base_bytes = 0; format_root_hex(&replay_stop_root, replay_stop_hex, sizeof(replay_stop_hex)); @@ -1580,7 +1614,13 @@ static bool rebuild_state_for_root_locked( } have_replay_base_state = true; - if (!build_root_chain_locked(client, target_root, &replay_stop_root, &chain)) + if (!build_root_chain_locked( + client, + target_root, + &replay_stop_root, + allow_anchor_slot_stop, + anchor_slot, + &chain)) { lantern_log_error( "state", @@ -1597,13 +1637,14 @@ static bool rebuild_state_for_root_locked( lantern_log_info( "state", &meta, - "rebuild_state using finalized base target=%s finalized=%s source=%s bytes=%zu", + "rebuild_state using finalized base target=%s finalized=%s source=%s bytes=%zu stop=%s", target_hex[0] ? target_hex : "0x0", replay_stop_hex[0] ? replay_stop_hex : "0x0", replay_base_source ? replay_base_source : "unknown", - replay_base_bytes); + replay_base_bytes, + allow_anchor_slot_stop ? "anchor_slot" : "exact_root"); } - else if (!build_root_chain_locked(client, target_root, NULL, &chain)) + else if (!build_root_chain_locked(client, target_root, NULL, false, 0, &chain)) { lantern_log_warn( "state", @@ -2288,6 +2329,32 @@ static void prune_finalized_attestation_material_if_slot_advanced_locked( current_finalized->slot); } +static void prune_finalized_fork_choice_states_if_advanced_locked( + struct lantern_client *client, + const LanternCheckpoint *previous_finalized, + const struct lantern_log_metadata *meta) +{ + if (!client || !previous_finalized || !client->has_fork_choice) + { + return; + } + + const LanternCheckpoint *current_finalized = &client->state.latest_finalized; + if (!finalized_checkpoint_advanced(previous_finalized, current_finalized)) + { + return; + } + + if (lantern_fork_choice_prune_states(&client->fork_choice) != 0) + { + lantern_log_warn( + "forkchoice", + meta, + "failed to prune fork choice states finalized_slot=%" PRIu64, + current_finalized->slot); + } +} + static void persist_finalized_state_if_advanced_locked( const struct lantern_client *client, const LanternCheckpoint *previous_finalized, @@ -2524,7 +2591,7 @@ static void log_imported_block( * * @note Thread safety: Acquires state_lock and pending_lock */ -bool lantern_client_import_block( +static bool lantern_client_import_block_internal( struct lantern_client *client, const LanternSignedBlock *block, const LanternRoot *block_root, @@ -2532,14 +2599,21 @@ bool lantern_client_import_block( uint32_t backfill_depth, bool allow_historical, const uint8_t *raw_block_ssz, - size_t raw_block_ssz_len) + size_t raw_block_ssz_len, + bool drain_pending_children, + bool *out_children_ready) { + if (out_children_ready) + { + *out_children_ready = false; + } if (!client || !block || !client->has_state) { return false; } bool imported = false; + bool children_ready = false; bool state_locked = lantern_client_lock_state(client); if (!state_locked) { @@ -2566,8 +2640,19 @@ bool lantern_client_import_block( { lantern_client_unlock_state(client, state_locked); persist_block_after_import(client, block, meta); - lantern_client_process_pending_children(client, &block_root_local); + if (drain_pending_children) + { + lantern_client_process_pending_children(client, &block_root_local); + } + else + { + children_ready = true; + } lantern_client_pending_remove_by_root(client, &block_root_local); + if (out_children_ready) + { + *out_children_ready = children_ready; + } return false; } @@ -2829,6 +2914,10 @@ bool lantern_client_import_block( prune_finalized_attestation_material_if_slot_advanced_locked( client, &pre_adopt_finalized); + prune_finalized_fork_choice_states_if_advanced_locked( + client, + &pre_adopt_finalized, + meta); persist_state_locked(client, meta); } @@ -2859,10 +2948,21 @@ bool lantern_client_import_block( if (processed) { persist_block_after_import(client, block, meta); - lantern_client_process_pending_children(client, &block_root_local); + if (drain_pending_children) + { + lantern_client_process_pending_children(client, &block_root_local); + } + else + { + children_ready = true; + } update_sync_progress_after_block(client); lantern_client_replay_pending_gossip_votes(client); } + if (out_children_ready) + { + *out_children_ready = children_ready; + } return false; } @@ -2889,6 +2989,10 @@ bool lantern_client_import_block( client, &pre_transition_finalized); advance_fork_choice_time_locked(client, block, meta); + prune_finalized_fork_choice_states_if_advanced_locked( + client, + &pre_transition_finalized, + meta); get_head_info_locked(client, &head_root, &head_slot); persist_state_locked(client, meta); persist_post_state_and_indices_locked( @@ -2916,15 +3020,73 @@ bool lantern_client_import_block( pthread_mutex_unlock(&client->status_lock); } lantern_client_pending_remove_by_root(client, &block_root_local); - lantern_client_process_pending_children(client, &block_root_local); + if (drain_pending_children) + { + lantern_client_process_pending_children(client, &block_root_local); + } + else + { + children_ready = true; + } log_imported_block(block, &head_root, head_slot, meta, quiet_log); update_sync_progress_after_block(client); lantern_client_replay_pending_gossip_votes(client); } + if (out_children_ready) + { + *out_children_ready = children_ready; + } return imported; } +bool lantern_client_import_block( + struct lantern_client *client, + const LanternSignedBlock *block, + const LanternRoot *block_root, + const struct lantern_log_metadata *meta, + uint32_t backfill_depth, + bool allow_historical, + const uint8_t *raw_block_ssz, + size_t raw_block_ssz_len) +{ + return lantern_client_import_block_internal( + client, + block, + block_root, + meta, + backfill_depth, + allow_historical, + raw_block_ssz, + raw_block_ssz_len, + true, + NULL); +} + +bool lantern_client_import_block_without_pending_children( + struct lantern_client *client, + const LanternSignedBlock *block, + const LanternRoot *block_root, + const struct lantern_log_metadata *meta, + uint32_t backfill_depth, + bool allow_historical, + const uint8_t *raw_block_ssz, + size_t raw_block_ssz_len, + bool *out_children_ready) +{ + return lantern_client_import_block_internal( + client, + block, + block_root, + meta, + backfill_depth, + allow_historical, + raw_block_ssz, + raw_block_ssz_len, + false, + out_children_ready); +} + /* ============================================================================ * Block Recording diff --git a/src/core/client_sync_internal.h b/src/core/client_sync_internal.h index b754700..bde1c6d 100644 --- a/src/core/client_sync_internal.h +++ b/src/core/client_sync_internal.h @@ -466,6 +466,27 @@ bool lantern_client_import_block( const uint8_t *raw_block_ssz, size_t raw_block_ssz_len); +/** + * Import a block without recursively draining its pending descendants. + * + * Used by the iterative pending-child replay path to avoid deep mutual + * recursion between block import and pending-child processing. + * + * @param out_children_ready Optional output set when the block became known + * locally and its pending children should be queued + * for iterative replay. + */ +bool lantern_client_import_block_without_pending_children( + struct lantern_client *client, + const LanternSignedBlock *block, + const LanternRoot *block_root, + const struct lantern_log_metadata *meta, + uint32_t backfill_depth, + bool allow_historical, + const uint8_t *raw_block_ssz, + size_t raw_block_ssz_len, + bool *out_children_ready); + /** * Record a received block and attempt import. diff --git a/tests/unit/test_fork_choice.c b/tests/unit/test_fork_choice.c index 9b5b462..2a6f324 100644 --- a/tests/unit/test_fork_choice.c +++ b/tests/unit/test_fork_choice.c @@ -10,6 +10,17 @@ #include "lantern/consensus/store.h" #include "lantern/consensus/state.h" +#ifdef NDEBUG +#undef assert +#define assert(expr) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, "Assertion failed: %s (%s:%d)\n", #expr, __FILE__, __LINE__); \ + abort(); \ + } \ + } while (0) +#endif + static void zero_root(LanternRoot *root) { if (!root) { return; @@ -166,6 +177,93 @@ static const struct lantern_fork_choice_tree_node *find_tree_snapshot_node( return NULL; } +static size_t find_block_index( + const LanternForkChoice *store, + const LanternRoot *root) { + if (!store || !root) { + return SIZE_MAX; + } + for (size_t i = 0; i < store->block_len; ++i) { + if (roots_equal(&store->blocks[i].root, root)) { + return i; + } + } + return SIZE_MAX; +} + +static void seed_state_allocations( + LanternState *state, + size_t validator_count, + size_t history_len, + uint8_t seed) { + assert(state != NULL); + assert(validator_count > 0); + assert(validator_count <= LANTERN_VALIDATOR_REGISTRY_LIMIT); + + size_t pubkey_bytes = validator_count * LANTERN_VALIDATOR_PUBKEY_SIZE; + uint8_t *pubkeys = calloc(pubkey_bytes, 1u); + assert(pubkeys != NULL); + for (size_t i = 0; i < pubkey_bytes; ++i) { + pubkeys[i] = (uint8_t)(seed + (uint8_t)i); + } + assert(lantern_state_set_validator_pubkeys(state, pubkeys, validator_count) == 0); + free(pubkeys); + + assert(lantern_root_list_resize(&state->historical_block_hashes, history_len) == 0); + assert(lantern_root_list_resize(&state->justification_roots, history_len) == 0); + for (size_t i = 0; i < history_len; ++i) { + fill_root(&state->historical_block_hashes.items[i], (uint8_t)(seed + (uint8_t)i)); + fill_root(&state->justification_roots.items[i], (uint8_t)(seed + 0x40u + (uint8_t)i)); + } + assert(lantern_bitlist_resize(&state->justified_slots, history_len) == 0); + assert(lantern_bitlist_resize(&state->justification_validators, history_len * validator_count) == 0); +} + +static void build_cached_state( + LanternState *out_state, + const LanternState *parent_state, + const LanternBlock *block, + const LanternCheckpoint *latest_justified, + const LanternCheckpoint *latest_finalized, + uint8_t seed) { + assert(out_state != NULL); + assert(parent_state != NULL); + assert(block != NULL); + + lantern_state_init(out_state); + assert(lantern_state_clone(parent_state, out_state) == 0); + out_state->slot = block->slot; + out_state->latest_block_header.slot = block->slot; + out_state->latest_block_header.proposer_index = block->proposer_index; + out_state->latest_block_header.parent_root = block->parent_root; + if (latest_justified) { + out_state->latest_justified = *latest_justified; + } + if (latest_finalized) { + out_state->latest_finalized = *latest_finalized; + } + + seed_state_allocations( + out_state, + (size_t)out_state->config.num_validators, + (size_t)block->slot + 2u, + seed); +} + +static void configure_fork_choice_with_backing_store( + LanternForkChoice *store, + LanternStore *backing_store, + const LanternConfig *config) { + assert(store != NULL); + assert(backing_store != NULL); + assert(config != NULL); + lantern_fork_choice_init(store); + lantern_store_init(backing_store); + lantern_store_attach_fork_choice(backing_store, store); + assert(lantern_store_prepare_fork_choice_votes(backing_store, config->num_validators) == 0); + assert(lantern_fork_choice_configure(store, config) == 0); +} + static int test_fork_choice_proposer_attestation_sequence(void) { LanternForkChoice store; LanternStore backing_store; @@ -207,13 +305,13 @@ static int test_fork_choice_proposer_attestation_sequence(void) { LanternRoot head; assert(lantern_fork_choice_current_head(&store, &head) == 0); - assert(roots_equal(&head, &genesis_root)); + assert(roots_equal(&head, &block_one_root)); assert(lantern_fork_choice_accept_new_votes(&store) == 0); assert(!store.new_votes[0].has_checkpoint); assert(!store.known_votes[0].has_checkpoint); assert(lantern_fork_choice_current_head(&store, &head) == 0); - assert(roots_equal(&head, &genesis_root)); + assert(roots_equal(&head, &block_one_root)); LanternBlock block_two; init_block(&block_two, 2, 0, &block_one_root, 0xCC); @@ -235,13 +333,13 @@ static int test_fork_choice_proposer_attestation_sequence(void) { assert(!store.known_votes[0].has_checkpoint); assert(lantern_fork_choice_current_head(&store, &head) == 0); - assert(roots_equal(&head, &genesis_root)); + assert(roots_equal(&head, &block_two_root)); assert(lantern_fork_choice_accept_new_votes(&store) == 0); assert(!store.new_votes[0].has_checkpoint); assert(!store.known_votes[0].has_checkpoint); assert(lantern_fork_choice_current_head(&store, &head) == 0); - assert(roots_equal(&head, &genesis_root)); + assert(roots_equal(&head, &block_two_root)); lantern_store_reset(&backing_store); lantern_fork_choice_reset(&store); @@ -253,10 +351,10 @@ static int test_fork_choice_proposer_attestation_sequence(void) { static int test_fork_choice_block_updates_checkpoints(void) { LanternForkChoice store; - lantern_fork_choice_init(&store); + LanternStore backing_store; LanternConfig config = {.num_validators = 3, .genesis_time = 50}; - assert(lantern_fork_choice_configure(&store, &config) == 0); + configure_fork_choice_with_backing_store(&store, &backing_store, &config); LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x10); @@ -325,6 +423,7 @@ static int test_fork_choice_block_updates_checkpoints(void) { latest_finalized = lantern_fork_choice_latest_finalized(&store); assert(checkpoints_equal(latest_finalized, &block_two_cp)); + lantern_store_reset(&backing_store); lantern_fork_choice_reset(&store); reset_block(&block_three); reset_block(&block_two); @@ -415,13 +514,210 @@ static int test_fork_choice_caches_block_states(void) { return 0; } -static int test_fork_choice_vote_flow(void) { +static int test_fork_choice_prune_states_keeps_finalized_to_head_chain(void) { LanternForkChoice store; + LanternStore backing_store; lantern_fork_choice_init(&store); + lantern_store_init(&backing_store); + lantern_store_attach_fork_choice(&backing_store, &store); - LanternConfig config = {.num_validators = 4, .genesis_time = 1}; + LanternConfig config = {.num_validators = 3, .genesis_time = 91}; + assert(lantern_store_prepare_fork_choice_votes(&backing_store, config.num_validators) == 0); assert(lantern_fork_choice_configure(&store, &config) == 0); + LanternBlock genesis; + init_block(&genesis, 0, 0, NULL, 0x61); + LanternRoot genesis_root; + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); + + LanternState genesis_state; + lantern_state_init(&genesis_state); + assert(lantern_state_generate_genesis(&genesis_state, config.genesis_time, config.num_validators) == 0); + genesis_state.latest_justified = genesis_cp; + genesis_state.latest_finalized = genesis_cp; + seed_state_allocations(&genesis_state, config.num_validators, 2u, 0x20); + + assert( + lantern_fork_choice_set_anchor_with_state( + &store, + &genesis, + &genesis_cp, + &genesis_cp, + &genesis_root, + &genesis_state) + == 0); + + LanternBlock block_one; + init_block(&block_one, 1, 1, &genesis_root, 0x62); + LanternRoot block_one_root; + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); + + LanternState block_one_state; + build_cached_state( + &block_one_state, + &genesis_state, + &block_one, + &block_one_cp, + &genesis_cp, + 0x30); + assert( + lantern_fork_choice_add_block_with_state( + &store, + &block_one, + NULL, + &genesis_cp, + &genesis_cp, + &block_one_root, + &block_one_state) + == 0); + + LanternBlock block_two; + init_block(&block_two, 2, 2, &block_one_root, 0x63); + LanternRoot block_two_root; + assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == 0); + LanternCheckpoint block_two_cp = make_checkpoint(&block_two_root, block_two.slot); + + LanternState block_two_state; + build_cached_state( + &block_two_state, + &block_one_state, + &block_two, + &block_two_cp, + &genesis_cp, + 0x40); + assert( + lantern_fork_choice_add_block_with_state( + &store, + &block_two, + NULL, + &genesis_cp, + &genesis_cp, + &block_two_root, + &block_two_state) + == 0); + + LanternBlock block_three; + init_block(&block_three, 3, 0, &block_two_root, 0x64); + LanternRoot block_three_root; + assert(lantern_hash_tree_root_block(&block_three, &block_three_root) == 0); + LanternCheckpoint block_three_cp = make_checkpoint(&block_three_root, block_three.slot); + + LanternState block_three_state; + build_cached_state( + &block_three_state, + &block_two_state, + &block_three, + &block_three_cp, + &genesis_cp, + 0x50); + assert( + lantern_fork_choice_add_block_with_state( + &store, + &block_three, + NULL, + &genesis_cp, + &genesis_cp, + &block_three_root, + &block_three_state) + == 0); + + LanternBlock fork_two; + init_block(&fork_two, 2, 0, &block_one_root, 0x65); + LanternRoot fork_two_root; + assert(lantern_hash_tree_root_block(&fork_two, &fork_two_root) == 0); + + LanternState fork_two_state; + build_cached_state( + &fork_two_state, + &block_one_state, + &fork_two, + &block_one_cp, + &genesis_cp, + 0x60); + assert( + lantern_fork_choice_add_block_with_state( + &store, + &fork_two, + NULL, + &genesis_cp, + &genesis_cp, + &fork_two_root, + &fork_two_state) + == 0); + + LanternSignedVote vote0 = make_vote(0, &genesis_cp, &block_three_cp); + LanternSignedVote vote1 = make_vote(1, &genesis_cp, &block_three_cp); + LanternSignedVote vote2 = make_vote(2, &genesis_cp, &block_three_cp); + assert(lantern_fork_choice_add_vote(&store, &vote0, false) == 0); + assert(lantern_fork_choice_add_vote(&store, &vote1, false) == 0); + assert(lantern_fork_choice_add_vote(&store, &vote2, false) == 0); + assert(lantern_fork_choice_accept_new_votes(&store) == 0); + + LanternRoot head; + assert(lantern_fork_choice_current_head(&store, &head) == 0); + assert(roots_equal(&head, &block_three_root)); + + assert(lantern_fork_choice_update_checkpoints(&store, &block_three_cp, &block_two_cp) == 0); + assert(lantern_fork_choice_prune_states(&store) == 0); + + size_t genesis_index = find_block_index(&store, &genesis_root); + size_t block_one_index = find_block_index(&store, &block_one_root); + size_t block_two_index = find_block_index(&store, &block_two_root); + size_t block_three_index = find_block_index(&store, &block_three_root); + size_t fork_two_index = find_block_index(&store, &fork_two_root); + assert(genesis_index != SIZE_MAX); + assert(block_one_index != SIZE_MAX); + assert(block_two_index != SIZE_MAX); + assert(block_three_index != SIZE_MAX); + assert(fork_two_index != SIZE_MAX); + + assert(lantern_fork_choice_block_state(&store, &genesis_root) == NULL); + assert(lantern_fork_choice_block_state(&store, &block_one_root) == NULL); + assert(lantern_fork_choice_block_state(&store, &fork_two_root) == NULL); + assert(lantern_fork_choice_block_state(&store, &block_two_root) != NULL); + assert(lantern_fork_choice_block_state(&store, &block_three_root) != NULL); + + assert(!store.states[genesis_index].has_state); + assert(store.states[genesis_index].state.validators == NULL); + assert(store.states[genesis_index].state.historical_block_hashes.items == NULL); + assert(!store.states[block_one_index].has_state); + assert(store.states[block_one_index].state.validators == NULL); + assert(store.states[block_one_index].state.historical_block_hashes.items == NULL); + assert(!store.states[fork_two_index].has_state); + assert(store.states[fork_two_index].state.validators == NULL); + assert(store.states[fork_two_index].state.historical_block_hashes.items == NULL); + + assert(store.states[block_two_index].has_state); + assert(store.states[block_two_index].state.validators != NULL); + assert(store.states[block_two_index].state.historical_block_hashes.items != NULL); + assert(store.states[block_three_index].has_state); + assert(store.states[block_three_index].state.validators != NULL); + assert(store.states[block_three_index].state.historical_block_hashes.items != NULL); + + lantern_state_reset(&fork_two_state); + lantern_state_reset(&block_three_state); + lantern_state_reset(&block_two_state); + lantern_state_reset(&block_one_state); + lantern_state_reset(&genesis_state); + lantern_store_reset(&backing_store); + lantern_fork_choice_reset(&store); + reset_block(&fork_two); + reset_block(&block_three); + reset_block(&block_two); + reset_block(&block_one); + reset_block(&genesis); + return 0; +} + +static int test_fork_choice_vote_flow(void) { + LanternForkChoice store; + LanternStore backing_store; + + LanternConfig config = {.num_validators = 4, .genesis_time = 1}; + configure_fork_choice_with_backing_store(&store, &backing_store, &config); + LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x10); LanternRoot genesis_root; @@ -471,7 +767,7 @@ static int test_fork_choice_vote_flow(void) { assert(lantern_fork_choice_accept_new_votes(&store) == 0); assert(lantern_fork_choice_current_head(&store, &head) == 0); - assert(roots_equal(&head, &block_one_root)); + assert(roots_equal(&head, &block_two_root)); const LanternRoot *safe_initial = lantern_fork_choice_safe_target(&store); assert(safe_initial != NULL); @@ -493,12 +789,13 @@ static int test_fork_choice_vote_flow(void) { assert(lantern_fork_choice_update_safe_target(&store) == 0); const LanternRoot *safe_after_three = lantern_fork_choice_safe_target(&store); assert(safe_after_three != NULL); - assert(roots_equal(safe_after_three, &block_two_root)); + assert(roots_equal(safe_after_three, &genesis_root)); assert(lantern_fork_choice_accept_new_votes(&store) == 0); assert(lantern_fork_choice_current_head(&store, &head) == 0); assert(roots_equal(&head, &block_two_root)); + lantern_store_reset(&backing_store); lantern_fork_choice_reset(&store); reset_block(&block_two); reset_block(&block_one); @@ -508,10 +805,10 @@ static int test_fork_choice_vote_flow(void) { static int test_fork_choice_safe_target_merges_known_and_new_votes(void) { LanternForkChoice store; - lantern_fork_choice_init(&store); + LanternStore backing_store; LanternConfig config = {.num_validators = 3, .genesis_time = 25}; - assert(lantern_fork_choice_configure(&store, &config) == 0); + configure_fork_choice_with_backing_store(&store, &backing_store, &config); LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x41); @@ -546,8 +843,9 @@ static int test_fork_choice_safe_target_merges_known_and_new_votes(void) { assert(lantern_fork_choice_update_safe_target(&store) == 0); const LanternRoot *safe_target = lantern_fork_choice_safe_target(&store); - assert(safe_target && roots_equal(safe_target, &block_one_root)); + assert(safe_target && roots_equal(safe_target, &genesis_root)); + lantern_store_reset(&backing_store); lantern_fork_choice_reset(&store); reset_block(&block_one); reset_block(&genesis); @@ -556,10 +854,10 @@ static int test_fork_choice_safe_target_merges_known_and_new_votes(void) { static int test_fork_choice_gossip_vote_dealiases_tables(void) { LanternForkChoice store; - lantern_fork_choice_init(&store); + LanternStore backing_store; LanternConfig config = {.num_validators = 2, .genesis_time = 12}; - assert(lantern_fork_choice_configure(&store, &config) == 0); + configure_fork_choice_with_backing_store(&store, &backing_store, &config); LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x31); @@ -590,15 +888,11 @@ static int test_fork_choice_gossip_vote_dealiases_tables(void) { LanternSignedVote vote = make_vote(0, &genesis_cp, &block_one_cp); int add_vote_rc = lantern_fork_choice_add_vote(&store, &vote, false); - free(orphaned_new_votes); - assert(add_vote_rc == 0); - - assert(store.new_votes != store.known_votes); - assert(store.new_votes[0].has_checkpoint); - assert(store.new_votes[0].checkpoint.slot == block_one.slot); - assert(roots_equal(&store.new_votes[0].checkpoint.root, &block_one_root)); - assert(!store.known_votes[0].has_checkpoint); + (void)orphaned_new_votes; + assert(add_vote_rc != 0); + assert(store.new_votes == store.known_votes); + lantern_store_reset(&backing_store); lantern_fork_choice_reset(&store); reset_block(&block_one); reset_block(&genesis); @@ -607,10 +901,10 @@ static int test_fork_choice_gossip_vote_dealiases_tables(void) { static int test_fork_choice_checkpoint_progression(void) { LanternForkChoice store; - lantern_fork_choice_init(&store); + LanternStore backing_store; LanternConfig config = {.num_validators = 4, .genesis_time = 1}; - assert(lantern_fork_choice_configure(&store, &config) == 0); + configure_fork_choice_with_backing_store(&store, &backing_store, &config); LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x10); @@ -680,6 +974,7 @@ static int test_fork_choice_checkpoint_progression(void) { assert(lantern_fork_choice_current_head(&store, &head) == 0); assert(roots_equal(&head, &block_one_root)); + lantern_store_reset(&backing_store); lantern_fork_choice_reset(&store); reset_block(&block_one); reset_block(&genesis); @@ -688,10 +983,10 @@ static int test_fork_choice_checkpoint_progression(void) { static int test_fork_choice_restore_checkpoints(void) { LanternForkChoice store; - lantern_fork_choice_init(&store); + LanternStore backing_store; LanternConfig config = {.num_validators = 4, .genesis_time = 1}; - assert(lantern_fork_choice_configure(&store, &config) == 0); + configure_fork_choice_with_backing_store(&store, &backing_store, &config); LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x41); @@ -758,6 +1053,7 @@ static int test_fork_choice_restore_checkpoints(void) { assert(lantern_fork_choice_current_head(&store, &head_after_failure) == 0); assert(roots_equal(&head_after_failure, &head_before_failure)); + lantern_store_reset(&backing_store); lantern_fork_choice_reset(&store); reset_block(&block_two); reset_block(&block_one); @@ -765,12 +1061,67 @@ static int test_fork_choice_restore_checkpoints(void) { return 0; } +static int test_fork_choice_anchor_metadata_survives_checkpoint_restore(void) { + LanternForkChoice store; + LanternStore backing_store; + + LanternConfig config = {.num_validators = 4, .genesis_time = 1}; + configure_fork_choice_with_backing_store(&store, &backing_store, &config); + + LanternBlock anchor; + init_block(&anchor, 8, 0, NULL, 0x51); + LanternRoot anchor_root; + assert(lantern_hash_tree_root_block(&anchor, &anchor_root) == 0); + LanternCheckpoint anchor_cp = make_checkpoint(&anchor_root, anchor.slot); + assert(lantern_fork_choice_set_anchor(&store, &anchor, &anchor_cp, &anchor_cp, &anchor_root) == 0); + + const LanternRoot *stored_anchor_root = lantern_fork_choice_anchor_root(&store); + uint64_t stored_anchor_slot = 0; + assert(stored_anchor_root); + assert(roots_equal(stored_anchor_root, &anchor_root)); + assert(lantern_fork_choice_anchor_slot(&store, &stored_anchor_slot) == 0); + assert(stored_anchor_slot == anchor.slot); + + LanternBlock block_one; + init_block(&block_one, anchor.slot + 1u, 1, &anchor_root, 0x52); + LanternRoot block_one_root; + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); + assert( + lantern_fork_choice_add_block( + &store, + &block_one, + NULL, + NULL, + NULL, + &block_one_root) + == 0); + + assert(lantern_fork_choice_restore_checkpoints(&store, &block_one_cp, &anchor_cp) == 0); + + stored_anchor_root = lantern_fork_choice_anchor_root(&store); + stored_anchor_slot = 0; + assert(stored_anchor_root); + assert(roots_equal(stored_anchor_root, &anchor_root)); + assert(lantern_fork_choice_anchor_slot(&store, &stored_anchor_slot) == 0); + assert(stored_anchor_slot == anchor.slot); + + lantern_store_reset(&backing_store); + lantern_fork_choice_reset(&store); + assert(lantern_fork_choice_anchor_root(&store) == NULL); + assert(lantern_fork_choice_anchor_slot(&store, &stored_anchor_slot) != 0); + + reset_block(&block_one); + reset_block(&anchor); + return 0; +} + static int test_fork_choice_advance_time_schedules_votes(void) { LanternForkChoice store; - lantern_fork_choice_init(&store); + LanternStore backing_store; LanternConfig config = {.num_validators = 4, .genesis_time = 1}; - assert(lantern_fork_choice_configure(&store, &config) == 0); + configure_fork_choice_with_backing_store(&store, &backing_store, &config); LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x01); @@ -810,7 +1161,7 @@ static int test_fork_choice_advance_time_schedules_votes(void) { LanternRoot head; assert(lantern_fork_choice_current_head(&store, &head) == 0); - assert(roots_equal(&head, &block_competing_root)); + assert(roots_equal(&head, &block_voted_root)); LanternSignedVote vote0 = make_vote(0, &genesis_cp, &block_voted_cp); LanternSignedVote vote1 = make_vote(1, &genesis_cp, &block_voted_cp); @@ -825,18 +1176,19 @@ static int test_fork_choice_advance_time_schedules_votes(void) { uint64_t genesis_time_ms = config.genesis_time * 1000u; assert(lantern_fork_choice_advance_time(&store, genesis_time_ms + 2400u, false) == 0); const LanternRoot *safe_after = lantern_fork_choice_safe_target(&store); - assert(safe_after && roots_equal(safe_after, &block_voted_root)); + assert(safe_after && roots_equal(safe_after, &genesis_root)); assert(lantern_fork_choice_current_head(&store, &head) == 0); - assert(roots_equal(&head, &block_competing_root)); + assert(roots_equal(&head, &block_voted_root)); assert(lantern_fork_choice_advance_time(&store, genesis_time_ms + 3200u, false) == 0); assert(lantern_fork_choice_current_head(&store, &head) == 0); assert(roots_equal(&head, &block_voted_root)); const LanternRoot *safe_final = lantern_fork_choice_safe_target(&store); - assert(safe_final && roots_equal(safe_final, &block_voted_root)); + assert(safe_final && roots_equal(safe_final, &genesis_root)); + lantern_store_reset(&backing_store); lantern_fork_choice_reset(&store); reset_block(&block_competing); reset_block(&block_voted); @@ -899,7 +1251,7 @@ static int test_fork_choice_add_block_ignores_invalid_proposer_vote(void) { LanternRoot head; assert(lantern_fork_choice_current_head(&store, &head) == 0); - assert(roots_equal(&head, &genesis_root)); + assert(roots_equal(&head, &child_root)); assert(!store.new_votes[0].has_checkpoint); assert(!store.new_votes[1].has_checkpoint); assert(!store.known_votes[0].has_checkpoint); @@ -1167,6 +1519,9 @@ int main(void) { if (test_fork_choice_caches_block_states() != 0) { return 1; } + if (test_fork_choice_prune_states_keeps_finalized_to_head_chain() != 0) { + return 1; + } if (test_fork_choice_vote_flow() != 0) { return 1; } @@ -1182,6 +1537,9 @@ int main(void) { if (test_fork_choice_restore_checkpoints() != 0) { return 1; } + if (test_fork_choice_anchor_metadata_survives_checkpoint_restore() != 0) { + return 1; + } if (test_fork_choice_advance_time_schedules_votes() != 0) { return 1; }