From 28a8e564f132562be99a65845136095c8125275e Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:54:23 +1000 Subject: [PATCH 1/3] Improve sync recovery for missing parents and BlocksByRoot handling --- .dockerignore | 3 + include/lantern/core/client.h | 1 + src/consensus/fork_choice.c | 195 ++++++++++++++++++- src/consensus/signature.c | 3 - src/core/client_pending.c | 1 + src/core/client_reqresp.c | 99 +++++++--- src/core/client_reqresp_blocks.c | 24 ++- src/core/client_sync.c | 85 ++++++++- src/core/client_sync_blocks.c | 311 +++++++++++++++++++++++++++---- src/core/client_sync_internal.h | 47 +++++ src/core/client_sync_votes.c | 130 ++++++++++--- src/core/client_utils.c | 5 + tests/unit/test_fork_choice.c | 60 ++++++ 13 files changed, 855 insertions(+), 109 deletions(-) diff --git a/.dockerignore b/.dockerignore index b0685bb..99afa02 100644 --- a/.dockerignore +++ b/.dockerignore @@ -26,3 +26,6 @@ external-clients/qlean-mini/example/1-network/genesis/ # Local docs/artifacts not needed in container build context internal-docs/ +tools/leanSpec/ +tools/lean-quickstart/ +external-clients/ diff --git a/include/lantern/core/client.h b/include/lantern/core/client.h index bd8cbc8..7f645be 100644 --- a/include/lantern/core/client.h +++ b/include/lantern/core/client.h @@ -89,6 +89,7 @@ struct lantern_pending_block { LanternRoot parent_root; char peer_text[128]; bool parent_requested; + uint32_t parent_request_failures; uint64_t received_ms; uint32_t backfill_depth; }; diff --git a/src/consensus/fork_choice.c b/src/consensus/fork_choice.c index 690a17e..c2821b8 100644 --- a/src/consensus/fork_choice.c +++ b/src/consensus/fork_choice.c @@ -193,6 +193,33 @@ static int map_insert(LanternForkChoice *store, const LanternRoot *root, size_t } } +static bool map_remove(LanternForkChoice *store, const LanternRoot *root) { + if (!store || !root || store->index_cap == 0 || !store->index_entries) { + return false; + } + uint64_t hash = root_hash(root); + size_t mask = store->index_cap - 1; + size_t pos = (size_t)(hash & mask); + size_t start = pos; + while (true) { + struct lantern_fork_choice_root_index_entry *entry = &store->index_entries[pos]; + if (!entry->occupied) { + return false; + } + if (!entry->tombstone && root_compare(&entry->root, root) == 0) { + entry->tombstone = true; + if (store->index_len > 0) { + store->index_len -= 1; + } + return true; + } + pos = (pos + 1) & mask; + if (pos == start) { + return false; + } + } +} + static void votes_reset(struct lantern_fork_choice_vote_entry *entries, size_t count) { if (!entries) { return; @@ -203,6 +230,86 @@ static void votes_reset(struct lantern_fork_choice_vote_entry *entries, size_t c } } +struct vote_undo_entry { + size_t validator_index; + struct lantern_fork_choice_vote_entry previous_known; + struct lantern_fork_choice_vote_entry previous_new; +}; + +struct vote_undo_list { + struct vote_undo_entry *entries; + size_t length; + size_t capacity; +}; + +static void vote_undo_reset(struct vote_undo_list *undo) { + if (!undo) { + return; + } + free(undo->entries); + undo->entries = NULL; + undo->length = 0; + undo->capacity = 0; +} + +static int vote_undo_save( + struct vote_undo_list *undo, + uint8_t *touched, + size_t touched_bytes, + LanternForkChoice *store, + size_t validator_index) { + if (!undo || !store) { + return -1; + } + if (!touched || touched_bytes == 0) { + return 0; + } + if (validator_index >= store->validator_count) { + return 0; + } + size_t byte = validator_index / 8u; + if (byte >= touched_bytes) { + return -1; + } + uint8_t bit = (uint8_t)(1u << (validator_index % 8u)); + if ((touched[byte] & bit) != 0) { + return 0; + } + touched[byte] |= bit; + + if (undo->length == undo->capacity) { + size_t next = undo->capacity == 0 ? 8u : (undo->capacity + (undo->capacity / 2u)); + if (next < undo->capacity) { + return -1; + } + struct vote_undo_entry *expanded = realloc(undo->entries, next * sizeof(*expanded)); + if (!expanded) { + return -1; + } + undo->entries = expanded; + undo->capacity = next; + } + undo->entries[undo->length].validator_index = validator_index; + undo->entries[undo->length].previous_known = store->known_votes[validator_index]; + undo->entries[undo->length].previous_new = store->new_votes[validator_index]; + undo->length += 1; + return 0; +} + +static void vote_undo_restore(const struct vote_undo_list *undo, LanternForkChoice *store) { + if (!undo || !store) { + return; + } + for (size_t i = 0; i < undo->length; ++i) { + size_t validator_index = undo->entries[i].validator_index; + if (validator_index >= store->validator_count) { + continue; + } + store->known_votes[validator_index] = undo->entries[i].previous_known; + store->new_votes[validator_index] = undo->entries[i].previous_new; + } +} + static int ensure_block_capacity(LanternForkChoice *store, size_t required) { if (!store) { return -1; @@ -393,6 +500,7 @@ static int register_block( size_t new_index = store->block_len; store->block_len += 1; if (map_insert(store, root, new_index) != 0) { + store->block_len -= 1; return -1; } update_children_parent_index(store, root, new_index); @@ -524,6 +632,37 @@ int lantern_fork_choice_add_block( return -1; } } + + LanternCheckpoint previous_justified = store->latest_justified; + LanternCheckpoint previous_finalized = store->latest_finalized; + LanternRoot previous_head = store->head; + bool had_head = store->has_head; + + size_t existing_index = 0; + bool existed = map_lookup(store, &block_root, &existing_index); + struct lantern_fork_choice_block_entry previous_entry; + memset(&previous_entry, 0, sizeof(previous_entry)); + if (existed) { + if (!store->blocks || existing_index >= store->block_len) { + return -1; + } + previous_entry = store->blocks[existing_index]; + } + + size_t previous_block_len = store->block_len; + + size_t validator_count = store->validator_count; + size_t touched_bytes = validator_count / 8u + (validator_count % 8u != 0 ? 1u : 0u); + uint8_t *touched = NULL; + if (touched_bytes > 0) { + touched = calloc(touched_bytes, 1); + if (!touched) { + return -1; + } + } + + struct vote_undo_list undo = {0}; + if (trace_finalization) { format_root_hex(&block_root, block_hex, sizeof(block_hex)); format_root_hex(&block->parent_root, parent_hex, sizeof(parent_hex)); @@ -533,10 +672,12 @@ int lantern_fork_choice_add_block( } } if (register_block(store, &block_root, &block->parent_root, block->slot, post_justified, post_finalized) != 0) { + free(touched); + vote_undo_reset(&undo); return -1; } if (update_global_checkpoints(store, post_justified, post_finalized) != 0) { - return -1; + goto rollback; } LanternAttestations expanded; @@ -546,25 +687,36 @@ int lantern_fork_choice_add_block( store->validator_count, &expanded) != 0) { - lantern_attestations_reset(&expanded); - return -1; + goto rollback_attestations; } for (size_t i = 0; i < expanded.length; ++i) { + size_t validator_index = (size_t)expanded.data[i].validator_id; + if (vote_undo_save(&undo, touched, touched_bytes, store, validator_index) != 0) { + lantern_attestations_reset(&expanded); + goto rollback; + } LanternSignedVote wrapped_vote; memset(&wrapped_vote, 0, sizeof(wrapped_vote)); wrapped_vote.data = expanded.data[i]; if (lantern_fork_choice_add_vote(store, &wrapped_vote, true) != 0) { lantern_attestations_reset(&expanded); - return -1; + goto rollback; } } lantern_attestations_reset(&expanded); if (lantern_fork_choice_recompute_head(store) != 0) { - return -1; + goto rollback; + } + + if (proposer_attestation) { + size_t proposer_index = (size_t)block->proposer_index; + if (vote_undo_save(&undo, touched, touched_bytes, store, proposer_index) != 0) { + goto rollback; + } } if (lantern_fork_choice_stage_proposer_vote(store, proposer_attestation, block->slot, block->proposer_index) != 0) { - return -1; + goto rollback; } lean_metrics_record_fork_choice_block_time(lantern_time_now_seconds() - metrics_start); if (trace_finalization) { @@ -583,7 +735,38 @@ int lantern_fork_choice_add_block( post_justified ? post_justified->slot : 0u, post_finalized ? post_finalized->slot : 0u); } + free(touched); + vote_undo_reset(&undo); return 0; + +rollback_attestations: + lantern_attestations_reset(&expanded); + +rollback: + store->latest_justified = previous_justified; + store->latest_finalized = previous_finalized; + store->head = previous_head; + store->has_head = had_head; + + vote_undo_restore(&undo, store); + + if (existed) { + store->blocks[existing_index] = previous_entry; + } else { + size_t new_index = store->block_len > 0 ? (store->block_len - 1u) : 0u; + for (size_t i = 0; i + 1u < store->block_len; ++i) { + struct lantern_fork_choice_block_entry *entry = &store->blocks[i]; + if (entry->parent_index == new_index && root_compare(&entry->parent_root, &block_root) == 0) { + entry->parent_index = SIZE_MAX; + } + } + map_remove(store, &block_root); + store->block_len = previous_block_len; + } + + free(touched); + vote_undo_reset(&undo); + return -1; } int lantern_fork_choice_add_vote( diff --git a/src/consensus/signature.c b/src/consensus/signature.c index 4b895a2..166e940 100644 --- a/src/consensus/signature.c +++ b/src/consensus/signature.c @@ -118,9 +118,6 @@ bool lantern_signature_verify( sizeof(signature->bytes)); double elapsed = get_time_seconds() - start; lean_metrics_record_pq_signature_verification(elapsed); - if (verify_rc != 1) { - lantern_log_debug("signature", NULL, "pq_verify_ssz rc=%d", verify_rc); - } return verify_rc == 1; } diff --git a/src/core/client_pending.c b/src/core/client_pending.c index 58c7634..0f13669 100644 --- a/src/core/client_pending.c +++ b/src/core/client_pending.c @@ -772,6 +772,7 @@ struct lantern_pending_block *pending_block_list_append( entry->parent_root = *parent_root; entry->peer_text[0] = '\0'; entry->parent_requested = false; + entry->parent_request_failures = 0; entry->received_ms = monotonic_millis(); entry->backfill_depth = backfill_depth; diff --git a/src/core/client_reqresp.c b/src/core/client_reqresp.c index c8886e0..968078f 100644 --- a/src/core/client_reqresp.c +++ b/src/core/client_reqresp.c @@ -38,6 +38,10 @@ #include "lantern/support/log.h" #include "lantern/support/strings.h" +enum { + ROOT_HEX_BUFFER_LEN = (LANTERN_ROOT_SIZE * 2u) + 3u, +}; + /* ============================================================================ * Sync Logging @@ -88,9 +92,11 @@ static bool lantern_client_update_blocks_request_tracking( uint32_t *out_failure_count, bool *out_entry_found); static const char *lantern_blocks_request_outcome_text(enum lantern_blocks_request_outcome outcome); -static void lantern_client_clear_pending_parent_requested( +static void lantern_client_handle_pending_parent_request_result( struct lantern_client *client, - const LanternRoot *request_root); + const LanternRoot *request_roots, + size_t root_count, + enum lantern_blocks_request_outcome outcome); static void lantern_client_request_status_after_blocks_success( struct lantern_client *client, const char *peer_id); @@ -904,10 +910,11 @@ static const char *lantern_blocks_request_outcome_text(enum lantern_blocks_reque * * @note Thread safety: This function acquires pending_lock */ -static void lantern_client_clear_pending_parent_requested_roots( +static void lantern_client_handle_pending_parent_request_result( struct lantern_client *client, const LanternRoot *request_roots, - size_t root_count) + size_t root_count, + enum lantern_blocks_request_outcome outcome) { if (!client || !request_roots || root_count == 0) { @@ -920,9 +927,13 @@ static void lantern_client_clear_pending_parent_requested_roots( return; } - for (size_t i = 0; i < client->pending_blocks.length; ++i) + bool track_empty = outcome == LANTERN_BLOCKS_REQUEST_EMPTY; + struct lantern_pending_block_list *list = &client->pending_blocks; + + for (size_t i = list->length; i-- > 0;) { - struct lantern_pending_block *entry = &client->pending_blocks.items[i]; + struct lantern_pending_block *entry = &list->items[i]; + bool matches = false; for (size_t j = 0; j < root_count; ++j) { if (lantern_root_is_zero(&request_roots[j])) @@ -931,32 +942,49 @@ static void lantern_client_clear_pending_parent_requested_roots( } if (memcmp(entry->parent_root.bytes, request_roots[j].bytes, LANTERN_ROOT_SIZE) == 0) { - entry->parent_requested = false; + matches = true; break; } } - } + if (!matches) + { + continue; + } - lantern_client_unlock_pending(client, locked); -} + entry->parent_requested = false; + if (!track_empty) + { + continue; + } -/** - * @brief Clear parent_requested flags for pending blocks matching request_root. - * - * @param client Client instance - * @param request_root Requested parent root - * - * @note Thread safety: This function acquires pending_lock - */ -static void lantern_client_clear_pending_parent_requested( - struct lantern_client *client, - const LanternRoot *request_root) -{ - if (!request_root) - { - return; + if (entry->parent_request_failures < UINT32_MAX) + { + entry->parent_request_failures += 1u; + } + if (entry->parent_request_failures < LANTERN_MAX_PENDING_PARENT_EMPTY_RESPONSES) + { + continue; + } + + char root_hex[ROOT_HEX_BUFFER_LEN]; + char parent_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(&entry->root, root_hex, sizeof(root_hex)); + format_root_hex(&entry->parent_root, parent_hex, sizeof(parent_hex)); + struct lantern_log_metadata meta = { + .validator = client->node_id, + .peer = entry->peer_text[0] ? entry->peer_text : NULL, + }; + lantern_log_warn( + "sync", + &meta, + "dropping pending block after empty parent fetches root=%s parent=%s attempts=%" PRIu32, + root_hex[0] ? root_hex : "0x0", + parent_hex[0] ? parent_hex : "0x0", + entry->parent_request_failures); + pending_block_list_remove(list, i); } - lantern_client_clear_pending_parent_requested_roots(client, request_root, 1u); + + lantern_client_unlock_pending(client, locked); } @@ -1237,6 +1265,19 @@ static void lantern_client_on_peer_status( bool head_known = false; peer_status_local_view(client, &peer_status->head.root, &local_slot, &head_known); + struct lantern_log_metadata meta = { + .validator = client->node_id, + .peer = peer_copy[0] ? peer_copy : NULL, + }; + lantern_log_debug( + "sync", + &meta, + "peer status view head_slot=%" PRIu64 " finalized_slot=%" PRIu64 " head_known=%s local_slot=%" PRIu64, + peer_status->head.slot, + peer_status->finalized.slot, + head_known ? "true" : "false", + local_slot); + /* If we bootstrapped via genesis fallback and the peer advertises the genesis head, adopt the peer's head root as our anchor so that subsequent block requests use the correct root. */ @@ -1408,7 +1449,11 @@ void lantern_client_on_blocks_request_complete_batch( failure_count); } - lantern_client_clear_pending_parent_requested_roots(client, request_roots, root_count); + lantern_client_handle_pending_parent_request_result( + client, + request_roots, + root_count, + outcome); if (outcome == LANTERN_BLOCKS_REQUEST_SUCCESS && peer_id && peer_id[0] != '\0') { diff --git a/src/core/client_reqresp_blocks.c b/src/core/client_reqresp_blocks.c index aaf4f99..f058578 100644 --- a/src/core/client_reqresp_blocks.c +++ b/src/core/client_reqresp_blocks.c @@ -41,6 +41,10 @@ /* ============================================================================ * Constants * ============================================================================ */ +enum +{ + ROOT_HEX_BUFFER_LEN = (LANTERN_ROOT_SIZE * 2u) + 3u, +}; /* ============================================================================ * Forward Declarations @@ -640,12 +644,22 @@ static void block_request_on_open(libp2p_stream_t *stream, void *user_data, int err); if (err != 0 || !stream) { + char root_hex[ROOT_HEX_BUFFER_LEN]; + root_hex[0] = '\0'; + if (ctx->root_count > 0) + { + format_root_hex(&ctx->roots[0], root_hex, sizeof(root_hex)); + } + const char *reason = connection_reason_text(err); lantern_log_warn( "reqresp", &meta, - "failed to open %s stream err=%d", + "failed to open %s stream err=%d (%s) roots=%zu first_root=%s", ctx->protocol_id, - err); + err, + reason ? reason : "-", + ctx->root_count, + root_hex[0] ? root_hex : "0x0"); if (stream) { libp2p_stream_free(stream); @@ -831,13 +845,15 @@ static int schedule_blocks_request_batch( ctx); if (rc != 0) { + const char *reason = connection_reason_text(rc); lantern_log_warn( "reqresp", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = ctx->peer_text[0] ? ctx->peer_text : NULL}, - "libp2p open stream failed rc=%d", - rc); + "libp2p open stream failed rc=%d (%s)", + rc, + reason ? reason : "-"); block_request_ctx_free(ctx); return LANTERN_CLIENT_ERR_NETWORK; } diff --git a/src/core/client_sync.c b/src/core/client_sync.c index af777b8..53a13ed 100644 --- a/src/core/client_sync.c +++ b/src/core/client_sync.c @@ -1138,9 +1138,28 @@ static bool try_schedule_blocks_request_batch( entry = lantern_client_ensure_status_entry_locked(client, peer_text); if (entry) { - if (!lantern_client_is_peer_connected(client, peer_text) - || entry->blocks_requests_inflight >= LANTERN_MAX_BLOCKS_REQUESTS_PER_PEER) + bool connected = lantern_client_is_peer_connected(client, peer_text); + bool inflight_full = + entry->blocks_requests_inflight >= LANTERN_MAX_BLOCKS_REQUESTS_PER_PEER; + if (!connected || inflight_full) { + uint64_t age_ms = 0; + if (entry->last_status_ms != 0 && now_ms >= entry->last_status_ms) + { + age_ms = now_ms - entry->last_status_ms; + } + lantern_log_info( + "reqresp", + &(const struct lantern_log_metadata){ + .validator = client->node_id, + .peer = peer_text}, + "blocks_by_root requested peer not eligible connected=%s inflight=%" PRIu32 + " failures=%" PRIu32 " has_status=%s status_age_ms=%" PRIu64, + connected ? "true" : "false", + entry->blocks_requests_inflight, + entry->consecutive_blocks_failures, + entry->has_status ? "true" : "false", + age_ms); entry = NULL; } } @@ -1252,13 +1271,57 @@ static bool try_schedule_blocks_request_batch( entry = best_entry; if (!entry) { + size_t connected_entries = 0; + size_t has_status_entries = 0; + size_t fresh_entries = 0; + size_t inflight_full_entries = 0; + size_t stale_entries = 0; + for (size_t i = 0; i < client->peer_status_count; ++i) + { + struct lantern_peer_status_entry *candidate = &client->peer_status_entries[i]; + if (!candidate->peer_id[0]) + { + continue; + } + if (candidate->blocks_requests_inflight >= LANTERN_MAX_BLOCKS_REQUESTS_PER_PEER) + { + inflight_full_entries += 1u; + } + bool connected = lantern_client_is_peer_connected(client, candidate->peer_id); + if (connected) + { + connected_entries += 1u; + } + if (candidate->has_status) + { + has_status_entries += 1u; + if (candidate->last_status_ms != 0 && now_ms >= candidate->last_status_ms) + { + uint64_t age_ms = now_ms - candidate->last_status_ms; + if (age_ms <= LANTERN_PEER_STATUS_STALE_MS) + { + fresh_entries += 1u; + } + else + { + stale_entries += 1u; + } + } + } + } lantern_log_info( "reqresp", &(const struct lantern_log_metadata){.validator = client->node_id}, - "blocks_by_root request skipped: no eligible peers roots=%zu connected=%zu status_entries=%zu", + "blocks_by_root request skipped: no eligible peers roots=%zu connected=%zu status_entries=%zu " + "connected_entries=%zu has_status=%zu fresh=%zu stale=%zu inflight_full=%zu", root_count, client->connected_peers, - client->peer_status_count); + client->peer_status_count, + connected_entries, + has_status_entries, + fresh_entries, + stale_entries, + inflight_full_entries); pthread_mutex_unlock(&client->status_lock); return false; } @@ -1313,6 +1376,16 @@ static bool try_schedule_blocks_request_batch( return true; } +bool lantern_client_try_schedule_blocks_request_batch( + struct lantern_client *client, + const char *peer_text, + const LanternRoot *roots, + const uint32_t *depths, + size_t root_count) +{ + return try_schedule_blocks_request_batch(client, peer_text, roots, depths, root_count); +} + static bool try_schedule_blocks_request( struct lantern_client *client, const char *peer_text, @@ -1625,9 +1698,10 @@ void lantern_client_request_pending_parent_after_blocks( return; } + const char *preferred_peer = (peer_text && peer_text[0]) ? peer_text : NULL; if (try_schedule_blocks_request_batch( client, - NULL, + preferred_peer, request_roots, request_depths, request_count)) @@ -1703,6 +1777,7 @@ void lantern_client_enqueue_pending_block( } } existing->received_ms = monotonic_millis(); + existing->parent_request_failures = 0; size_t pending_len = list->length; bool parent_requested = existing->parent_requested; lantern_client_unlock_pending(client, locked); diff --git a/src/core/client_sync_blocks.c b/src/core/client_sync_blocks.c index 66030c7..331d95b 100644 --- a/src/core/client_sync_blocks.c +++ b/src/core/client_sync_blocks.c @@ -114,6 +114,7 @@ extern bool lantern_client_verify_vote_signature( const struct lantern_client *client, const LanternSignedVote *vote, const LanternSignature *signature, + const LanternState *state_override, const struct lantern_log_metadata *meta, const char *context); @@ -151,12 +152,46 @@ static bool signed_block_signatures_are_valid( { return false; } + LanternState parent_state; + lantern_state_init(&parent_state); + const LanternState *state_for_sig = NULL; + const LanternRoot parent_root = block->message.block.parent_root; + + if (lantern_root_is_zero(&parent_root)) + { + if (client->has_state) + { + state_for_sig = &client->state; + } + } + else + { + state_for_sig = lantern_client_state_for_root_locked( + (struct lantern_client *)client, + &parent_root, + &parent_state, + NULL); + } + if (!state_for_sig) + { + char parent_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(&parent_root, parent_hex, sizeof(parent_hex)); + lantern_log_warn( + "state", + meta, + "missing parent state for signature verification parent_root=%s", + parent_hex[0] ? parent_hex : "0x0"); + lantern_state_reset(&parent_state); + return false; + } + const LanternAggregatedAttestations *attestations = &block->message.block.body.attestations; const LanternAttestationSignatures *sig_groups = &block->signatures.attestation_signatures; size_t att_count = attestations->length; if (!client->genesis.validator_registry.records) { + lantern_state_reset(&parent_state); return true; } if (att_count > 0 && !attestations->data) @@ -167,6 +202,7 @@ static bool signed_block_signatures_are_valid( "signed block slot=%" PRIu64 " attestations missing data length=%zu", block->message.block.slot, att_count); + lantern_state_reset(&parent_state); return false; } if (att_count != sig_groups->length) @@ -178,6 +214,7 @@ static bool signed_block_signatures_are_valid( block->message.block.slot, att_count, sig_groups->length); + lantern_state_reset(&parent_state); return false; } if (att_count > 0 && !sig_groups->data) @@ -188,10 +225,12 @@ static bool signed_block_signatures_are_valid( "signed block slot=%" PRIu64 " missing aggregated signatures length=%zu", block->message.block.slot, sig_groups->length); + lantern_state_reset(&parent_state); return false; } - size_t validator_count = lantern_state_validator_count(&client->state); + size_t validator_count = lantern_state_validator_count(state_for_sig); + const uint8_t **pubkeys = NULL; for (size_t i = 0; i < att_count; ++i) { @@ -204,6 +243,7 @@ static bool signed_block_signatures_are_valid( } if (proof->participants.bit_length != att->aggregation_bits.bit_length) { + lantern_state_reset(&parent_state); return false; } size_t bytes = (bit_length + 7u) / 8u; @@ -212,6 +252,7 @@ static bool signed_block_signatures_are_valid( if (!proof->participants.bytes || memcmp(proof->participants.bytes, att->aggregation_bits.bytes, bytes) != 0) { + lantern_state_reset(&parent_state); return false; } } @@ -225,11 +266,13 @@ static bool signed_block_signatures_are_valid( } if (participant_count == 0) { + lantern_state_reset(&parent_state); return false; } - const uint8_t **pubkeys = calloc(participant_count, sizeof(*pubkeys)); + pubkeys = calloc(participant_count, sizeof(*pubkeys)); if (!pubkeys) { + lantern_state_reset(&parent_state); return false; } size_t idx = 0; @@ -242,12 +285,14 @@ static bool signed_block_signatures_are_valid( if (v >= validator_count) { free(pubkeys); + lantern_state_reset(&parent_state); return false; } - const uint8_t *pubkey = lantern_state_validator_pubkey(&client->state, v); + const uint8_t *pubkey = lantern_state_validator_pubkey(state_for_sig, v); if (!pubkey || lantern_validator_pubkey_is_zero(pubkey)) { free(pubkeys); + lantern_state_reset(&parent_state); return false; } pubkeys[idx++] = pubkey; @@ -257,6 +302,7 @@ static bool signed_block_signatures_are_valid( if (lantern_hash_tree_root_attestation_data(&att->data, &data_root) != 0) { free(pubkeys); + lantern_state_reset(&parent_state); return false; } bool sig_ok = lantern_signature_verify_aggregated( @@ -267,8 +313,10 @@ static bool signed_block_signatures_are_valid( &proof->proof_data, att->data.slot); free(pubkeys); + pubkeys = NULL; if (!sig_ok) { + lantern_state_reset(&parent_state); return false; } } @@ -276,12 +324,15 @@ static bool signed_block_signatures_are_valid( LanternSignedVote proposer_signed = {0}; proposer_signed.data = block->message.proposer_attestation; proposer_signed.signature = block->signatures.proposer_signature; - return lantern_client_verify_vote_signature( + bool proposer_ok = lantern_client_verify_vote_signature( client, &proposer_signed, &proposer_signed.signature, + state_for_sig, meta, "proposer"); + lantern_state_reset(&parent_state); + return proposer_ok; } static void cache_block_aggregated_proofs_locked( @@ -324,7 +375,6 @@ static void persist_block_after_import( struct lantern_log_metadata fallback = {.validator = client->node_id}; const struct lantern_log_metadata *log_meta = meta ? meta : &fallback; - if (lantern_storage_store_block(client->data_dir, block) != 0) { lantern_log_warn( @@ -776,6 +826,106 @@ static bool compute_state_head_root_locked( return true; } +static bool rebuild_state_for_root_locked( + struct lantern_client *client, + const LanternRoot *target_root, + LanternState *out_state, + LanternRoot *out_missing_roots, + size_t missing_roots_cap, + size_t *out_missing_count); + +static bool state_matches_root(const LanternState *state, const LanternRoot *root) +{ + if (!state || !root) + { + return false; + } + LanternRoot state_root; + if (lantern_hash_tree_root_state(state, &state_root) != 0) + { + return false; + } + LanternBlockHeader header = state->latest_block_header; + header.state_root = state_root; + LanternRoot header_root; + if (lantern_hash_tree_root_block_header(&header, &header_root) != 0) + { + return false; + } + return memcmp(header_root.bytes, root->bytes, LANTERN_ROOT_SIZE) == 0; +} + +const LanternState *lantern_client_state_for_root_locked( + struct lantern_client *client, + const LanternRoot *root, + LanternState *scratch, + bool *out_is_scratch) +{ + if (out_is_scratch) + { + *out_is_scratch = false; + } + if (!client || !root) + { + return NULL; + } + + if (client->has_state && state_matches_root(&client->state, root)) + { + return &client->state; + } + + if (!scratch) + { + return NULL; + } + + if (client->data_dir && client->data_dir[0]) + { + uint8_t *state_bytes = NULL; + size_t state_len = 0; + if (lantern_storage_load_state_bytes_for_root( + client->data_dir, + root, + &state_bytes, + &state_len) + == 0 + && state_bytes + && state_len > 0) + { + lantern_state_reset(scratch); + lantern_state_init(scratch); + if (lantern_ssz_decode_state(scratch, state_bytes, state_len) == 0) + { + free(state_bytes); + if (out_is_scratch) + { + *out_is_scratch = true; + } + return scratch; + } + lantern_state_reset(scratch); + free(state_bytes); + } + } + + if (client->data_dir && client->data_dir[0]) + { + lantern_state_reset(scratch); + if (rebuild_state_for_root_locked(client, root, scratch, NULL, 0, NULL)) + { + if (out_is_scratch) + { + *out_is_scratch = true; + } + return scratch; + } + lantern_state_reset(scratch); + } + + return NULL; +} + static void adopt_state_locked(struct lantern_client *client, LanternState *state) { if (!client || !state) @@ -791,12 +941,19 @@ static void adopt_state_locked(struct lantern_client *client, LanternState *stat static bool rebuild_state_for_root_locked( struct lantern_client *client, const LanternRoot *target_root, - LanternState *out_state) + LanternState *out_state, + LanternRoot *out_missing_roots, + size_t missing_roots_cap, + size_t *out_missing_count) { if (!client || !target_root || !out_state) { return false; } + if (out_missing_count) + { + *out_missing_count = 0; + } struct lantern_log_metadata meta = {.validator = client->node_id}; char target_hex[ROOT_HEX_BUFFER_LEN]; @@ -877,24 +1034,32 @@ static bool rebuild_state_for_root_locked( collect_rc, root_count, response.length); - if (roots && response.length > 0) + if (roots && root_count > 0) { - LanternRoot *found_roots = calloc(response.length, sizeof(*found_roots)); - if (found_roots) + LanternRoot *found_roots = NULL; + size_t found_count = 0; + if (response.length > 0) { - size_t found_count = 0; - for (size_t i = 0; i < response.length; ++i) + found_roots = calloc(response.length, sizeof(*found_roots)); + if (found_roots) { - if (lantern_hash_tree_root_block(&response.blocks[i].message.block, &found_roots[i]) == 0) + for (size_t i = 0; i < response.length; ++i) { - found_count += 1u; + if (lantern_hash_tree_root_block(&response.blocks[i].message.block, &found_roots[i]) == 0) + { + found_count += 1u; + } } } - size_t missing = 0; - size_t logged = 0; - for (size_t i = 0; i < root_count; ++i) + } + size_t missing = 0; + size_t logged = 0; + size_t missing_filled = 0; + for (size_t i = 0; i < root_count; ++i) + { + bool present = false; + if (found_roots) { - bool present = false; for (size_t j = 0; j < response.length; ++j) { if (memcmp(roots[i].bytes, found_roots[j].bytes, LANTERN_ROOT_SIZE) == 0) @@ -903,30 +1068,39 @@ static bool rebuild_state_for_root_locked( break; } } - if (!present) + } + if (!present) + { + missing += 1u; + if (out_missing_roots && missing_filled < missing_roots_cap) { - missing += 1u; - if (logged < 5) - { - char missing_hex[ROOT_HEX_BUFFER_LEN]; - format_root_hex(&roots[i], missing_hex, sizeof(missing_hex)); - lantern_log_warn( - "state", - &meta, - "rebuild_state missing block root=%s", - missing_hex[0] ? missing_hex : "0x0"); - logged += 1u; - } + out_missing_roots[missing_filled] = roots[i]; + missing_filled += 1u; + } + if (logged < 5) + { + char missing_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(&roots[i], missing_hex, sizeof(missing_hex)); + lantern_log_warn( + "state", + &meta, + "rebuild_state missing block root=%s", + missing_hex[0] ? missing_hex : "0x0"); + logged += 1u; } } - lantern_log_warn( - "state", - &meta, - "rebuild_state missing_blocks=%zu found_blocks=%zu", - missing, - found_count); - free(found_roots); } + if (out_missing_count) + { + *out_missing_count = missing_filled; + } + lantern_log_warn( + "state", + &meta, + "rebuild_state missing_blocks=%zu found_blocks=%zu", + missing, + found_count); + free(found_roots); } } free(roots); @@ -1522,6 +1696,16 @@ bool lantern_client_import_block( uint64_t known_slot = 0; bool root_known = lantern_client_block_known_locked(client, &block_root_local, &known_slot); + if (root_known && allow_historical && block->message.block.slot <= known_slot) + { + lantern_client_unlock_state(client, state_locked); + state_locked = false; + persist_block_after_import(client, block, meta); + lantern_client_process_pending_children(client, &block_root_local); + lantern_client_pending_remove_by_root(client, &block_root_local); + return false; + } + if (!should_process_block( block->message.block.slot, local_slot, @@ -1582,8 +1766,17 @@ bool lantern_client_import_block( LanternState replay_state; bool have_replay_state = false; bool processed = false; + bool deferred = false; + LanternRoot missing_roots[LANTERN_MAX_BLOCKS_PER_REQUEST]; + size_t missing_count = 0; - if (rebuild_state_for_root_locked(client, &parent_root, &replay_state)) + if (rebuild_state_for_root_locked( + client, + &parent_root, + &replay_state, + missing_roots, + LANTERN_MAX_BLOCKS_PER_REQUEST, + &missing_count)) { have_replay_state = true; if (lantern_state_transition(&replay_state, block) == 0) @@ -1612,6 +1805,35 @@ bool lantern_client_import_block( meta, "failed to rebuild parent state for off-head slot=%" PRIu64, block->message.block.slot); + deferred = true; + const char *peer_text = meta && meta->peer ? meta->peer : NULL; + lantern_client_enqueue_pending_block( + client, + block, + &block_root_local, + &parent_root, + peer_text, + backfill_depth, + true); + if (missing_count > 0) + { + uint32_t request_depths[LANTERN_MAX_BLOCKS_PER_REQUEST]; + uint32_t request_depth = backfill_depth + 1u; + if (request_depth > LANTERN_MAX_BACKFILL_DEPTH) + { + request_depth = LANTERN_MAX_BACKFILL_DEPTH; + } + for (size_t i = 0; i < missing_count; ++i) + { + request_depths[i] = request_depth; + } + (void)lantern_client_try_schedule_blocks_request_batch( + client, + peer_text, + missing_roots, + request_depths, + missing_count); + } } bool adopted_state = false; @@ -1640,7 +1862,13 @@ bool lantern_client_import_block( else { LanternState head_state; - if (rebuild_state_for_root_locked(client, &fork_head, &head_state)) + if (rebuild_state_for_root_locked( + client, + &fork_head, + &head_state, + NULL, + 0, + NULL)) { adopt_state_locked(client, &head_state); adopted_state = true; @@ -1674,7 +1902,10 @@ bool lantern_client_import_block( lantern_client_unlock_state(client, state_locked); state_locked = false; - lantern_client_pending_remove_by_root(client, &block_root_local); + if (!deferred) + { + lantern_client_pending_remove_by_root(client, &block_root_local); + } if (processed) { persist_block_after_import(client, block, meta); diff --git a/src/core/client_sync_internal.h b/src/core/client_sync_internal.h index ac67a27..38db3a4 100644 --- a/src/core/client_sync_internal.h +++ b/src/core/client_sync_internal.h @@ -27,6 +27,7 @@ #include "lantern/core/client.h" #include "lantern/consensus/containers.h" +#include "lantern/consensus/state.h" #include "lantern/support/log.h" #include @@ -49,6 +50,8 @@ typedef struct peer_id peer_id_t; #define LANTERN_MAX_BLOCKS_PER_REQUEST 10u /** Maximum backfill depth when requesting parents */ #define LANTERN_MAX_BACKFILL_DEPTH LANTERN_PENDING_BLOCK_LIMIT +/** Maximum consecutive empty parent fetches before dropping pending blocks */ +#define LANTERN_MAX_PENDING_PARENT_EMPTY_RESPONSES 8u /* ============================================================================ @@ -135,6 +138,27 @@ bool lantern_client_block_known_locked( const LanternRoot *root, uint64_t *out_slot); +/** + * Get a state snapshot for a specific block root. + * + * Returns a pointer to either the client's current state (if it matches the root) + * or a scratch state populated from storage/replay. When a scratch state is used, + * the caller must reset it with lantern_state_reset(). + * + * @param client Client instance + * @param root Block root to resolve + * @param scratch Scratch state storage (must be initialized) + * @param out_is_scratch Output true if scratch is used + * @return Pointer to state to use, or NULL if unavailable + * + * @note Thread safety: Caller must hold state_lock + */ +const LanternState *lantern_client_state_for_root_locked( + struct lantern_client *client, + const LanternRoot *root, + LanternState *scratch, + bool *out_is_scratch); + /* ============================================================================ * Pending Block Functions @@ -505,6 +529,29 @@ void lantern_client_request_pending_parent_after_blocks( const char *peer_text, const LanternRoot *request_root); +/** + * Attempt to schedule a blocks_by_root request using peer selection. + * + * Selects an eligible peer based on status tracking and schedules a request + * for the provided roots. Returns false if no eligible peer is available or + * scheduling fails. + * + * @param client Client instance + * @param peer_text Preferred peer ID string (may be NULL) + * @param roots Block roots to request + * @param depths Backfill depth per root (may be NULL) + * @param root_count Number of roots + * @return true if scheduling succeeded, false otherwise + * + * @note Thread safety: This function is thread-safe + */ +bool lantern_client_try_schedule_blocks_request_batch( + struct lantern_client *client, + const char *peer_text, + const LanternRoot *roots, + const uint32_t *depths, + size_t root_count); + /** * Remove a pending block by root. diff --git a/src/core/client_sync_votes.c b/src/core/client_sync_votes.c index ab3d536..7139ee4 100644 --- a/src/core/client_sync_votes.c +++ b/src/core/client_sync_votes.c @@ -47,6 +47,7 @@ bool lantern_client_verify_vote_signature( const struct lantern_client *client, const LanternSignedVote *vote, const LanternSignature *signature, + const LanternState *state_override, const struct lantern_log_metadata *meta, const char *context); @@ -418,10 +419,37 @@ static bool process_vote_locked( return false; } + LanternState target_state; + lantern_state_init(&target_state); + const LanternState *sig_state = lantern_client_state_for_root_locked( + client, + &vote->data.target.root, + &target_state, + NULL); + if (!sig_state) + { + char target_hex[VOTE_ROOT_HEX_BUFFER_LEN]; + format_root_hex(&vote->data.target.root, target_hex, sizeof(target_hex)); + lantern_log_debug( + "gossip", + meta, + "dropping vote validator=%" PRIu64 " slot=%" PRIu64 " (missing target state root=%s)", + vote->data.validator_id, + vote->data.slot, + target_hex[0] ? target_hex : "0x0"); + lantern_vote_rejection_set( + rejection, + "missing target state root=%s", + target_hex[0] ? target_hex : "0x0"); + lantern_state_reset(&target_state); + return false; + } + if (!lantern_client_verify_vote_signature( client, vote, &vote->signature, + sig_state, meta, "gossip")) { @@ -432,8 +460,10 @@ static bool process_vote_locked( vote->data.validator_id, vote->data.slot); lantern_vote_rejection_set(rejection, "invalid XMSS signature"); + lantern_state_reset(&target_state); return false; } + lantern_state_reset(&target_state); if (!validate_vote_cache_state(client, &vote->data, meta, rejection)) { @@ -470,6 +500,7 @@ static bool process_vote_locked( * @param client Client instance * @param vote Signed vote to verify * @param signature Signature to verify + * @param state_override Optional state to use for validator pubkey lookup * @param meta Logging metadata * @param context Description of signature context for logging * @return true if signature is valid @@ -480,6 +511,7 @@ bool lantern_client_verify_vote_signature( const struct lantern_client *client, const LanternSignedVote *vote, const LanternSignature *signature, + const LanternState *state_override, const struct lantern_log_metadata *meta, const char *context) { @@ -488,47 +520,85 @@ bool lantern_client_verify_vote_signature( return false; } const uint8_t *pubkey_bytes = NULL; - bool state_has_registry = client->has_state; - size_t state_validator_count = 0; - if (state_has_registry) - { - state_validator_count = lantern_state_validator_count(&client->state); - } - if (state_has_registry && state_validator_count > 0) + if (state_override) { + size_t state_validator_count = lantern_state_validator_count(state_override); + if (state_validator_count == 0) + { + lantern_log_warn( + "state", + meta, + "missing validator registry for %s signature verification", + context ? context : "vote"); + return false; + } if (vote->data.validator_id >= state_validator_count) { lantern_log_warn( "state", meta, - "validator=%" PRIu64 " exceeds parent state validator count=%zu", + "validator=%" PRIu64 " exceeds target state validator count=%zu", vote->data.validator_id, state_validator_count); return false; } pubkey_bytes = lantern_state_validator_pubkey( - &client->state, + state_override, (size_t)vote->data.validator_id); - if (lantern_validator_pubkey_is_zero(pubkey_bytes)) - { - pubkey_bytes = NULL; - } - } - if (!pubkey_bytes) - { - const struct lantern_validator_record *record = - lantern_client_get_validator_record(client, vote->data.validator_id); - if (!record || !record->has_pubkey_bytes) + if (!pubkey_bytes || lantern_validator_pubkey_is_zero(pubkey_bytes)) { lantern_log_warn( "state", meta, - "missing validator %s pubkey for validator=%" PRIu64, - context ? context : "signature", - vote->data.validator_id); + "missing validator pubkey for %s signature verification", + context ? context : "vote"); return false; } - pubkey_bytes = record->pubkey_bytes; + } + else + { + bool state_has_registry = client->has_state; + size_t state_validator_count = 0; + if (state_has_registry) + { + state_validator_count = lantern_state_validator_count(&client->state); + } + if (state_has_registry && state_validator_count > 0) + { + if (vote->data.validator_id >= state_validator_count) + { + lantern_log_warn( + "state", + meta, + "validator=%" PRIu64 " exceeds parent state validator count=%zu", + vote->data.validator_id, + state_validator_count); + return false; + } + pubkey_bytes = lantern_state_validator_pubkey( + &client->state, + (size_t)vote->data.validator_id); + if (lantern_validator_pubkey_is_zero(pubkey_bytes)) + { + pubkey_bytes = NULL; + } + } + if (!pubkey_bytes) + { + const struct lantern_validator_record *record = + lantern_client_get_validator_record(client, vote->data.validator_id); + if (!record || !record->has_pubkey_bytes) + { + lantern_log_warn( + "state", + meta, + "missing validator %s pubkey for validator=%" PRIu64, + context ? context : "signature", + vote->data.validator_id); + return false; + } + pubkey_bytes = record->pubkey_bytes; + } } LanternRoot vote_root; if (lantern_hash_tree_root_attestation_data(&vote->data.data, &vote_root) != 0) @@ -552,8 +622,9 @@ bool lantern_client_verify_vote_signature( lantern_log_warn( "state", meta, - "invalid XMSS signature validator=%" PRIu64 " context=%s", + "invalid XMSS signature validator=%" PRIu64 " slot=%" PRIu64 " context=%s", vote->data.validator_id, + vote->data.slot, context ? context : "unknown"); } return is_signature_valid; @@ -804,5 +875,16 @@ void lantern_client_record_vote( vote_copy.data.source.slot, reason_text); } + if (rejection.has_unknown_root && !lantern_root_is_zero(&rejection.unknown_root)) + { + char root_hex[VOTE_ROOT_HEX_BUFFER_LEN]; + format_root_hex(&rejection.unknown_root, root_hex, sizeof(root_hex)); + lantern_log_info( + "reqresp", + &meta, + "dropping vote unknown root=%s slot=%" PRIu64 " (no backfill)", + root_hex[0] ? root_hex : "0x0", + rejection.unknown_slot); + } } } diff --git a/src/core/client_utils.c b/src/core/client_utils.c index 85e51c6..207e487 100644 --- a/src/core/client_utils.c +++ b/src/core/client_utils.c @@ -494,6 +494,11 @@ bool lantern_client_current_slot(const struct lantern_client *client, uint64_t * { return false; } + if (store->has_anchor && store->intervals_per_slot > 0) + { + *out_slot = store->time_intervals / store->intervals_per_slot; + return true; + } uint64_t now = validator_wall_time_now_seconds(); if (now == 0) { diff --git a/tests/unit/test_fork_choice.c b/tests/unit/test_fork_choice.c index cb81738..533f8b1 100644 --- a/tests/unit/test_fork_choice.c +++ b/tests/unit/test_fork_choice.c @@ -511,6 +511,63 @@ static int test_fork_choice_advance_time_schedules_votes(void) { return 0; } +static int test_fork_choice_add_block_is_atomic_on_failure(void) { + LanternForkChoice store; + lantern_fork_choice_init(&store); + + LanternConfig config = {.num_validators = 2, .genesis_time = 100}; + assert(lantern_fork_choice_configure(&store, &config) == 0); + + LanternBlock genesis; + init_block(&genesis, 0, 0, NULL, 0x01); + LanternRoot genesis_root; + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); + assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); + + LanternBlock parent; + init_block(&parent, 1, 0, &genesis_root, 0x02); + LanternRoot parent_root; + assert(lantern_hash_tree_root_block(&parent, &parent_root) == 0); + + LanternBlock child; + init_block(&child, 2, 1, &parent_root, 0x03); + LanternRoot child_root; + assert(lantern_hash_tree_root_block(&child, &child_root) == 0); + assert(lantern_fork_choice_add_block(&store, &child, NULL, NULL, NULL, &child_root) == 0); + + uint64_t child_slot = 0; + bool child_has_parent = true; + assert(lantern_fork_choice_block_info(&store, &child_root, &child_slot, NULL, &child_has_parent) == 0); + assert(child_slot == 2); + assert(child_has_parent == false); + + LanternSignedVote bad_proposer_vote; + memset(&bad_proposer_vote, 0, sizeof(bad_proposer_vote)); + bad_proposer_vote.data.validator_id = 1; /* wrong proposer index */ + bad_proposer_vote.data.slot = parent.slot; /* correct slot */ + bad_proposer_vote.data.target = make_checkpoint(&parent_root, parent.slot); + bad_proposer_vote.data.head = bad_proposer_vote.data.target; + + assert(lantern_fork_choice_add_block(&store, &parent, &bad_proposer_vote, NULL, NULL, &parent_root) != 0); + + uint64_t parent_slot = 0; + assert(lantern_fork_choice_block_info(&store, &parent_root, &parent_slot, NULL, NULL) != 0); + + assert(lantern_fork_choice_block_info(&store, &child_root, &child_slot, NULL, &child_has_parent) == 0); + assert(child_has_parent == false); + + LanternRoot head; + assert(lantern_fork_choice_current_head(&store, &head) == 0); + assert(roots_equal(&head, &genesis_root)); + + lantern_fork_choice_reset(&store); + reset_block(&child); + reset_block(&parent); + reset_block(&genesis); + return 0; +} + int main(void) { if (test_fork_choice_proposer_attestation_sequence() != 0) { return 1; @@ -527,5 +584,8 @@ int main(void) { if (test_fork_choice_advance_time_schedules_votes() != 0) { return 1; } + if (test_fork_choice_add_block_is_atomic_on_failure() != 0) { + return 1; + } return 0; } From 347e203f8c8da7fa25f5a7010288d17c95ec6177 Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:05:44 +1000 Subject: [PATCH 2/3] Update client_utils.c --- src/core/client_utils.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/client_utils.c b/src/core/client_utils.c index 207e487..1d82ca8 100644 --- a/src/core/client_utils.c +++ b/src/core/client_utils.c @@ -494,7 +494,9 @@ bool lantern_client_current_slot(const struct lantern_client *client, uint64_t * { return false; } - if (store->has_anchor && store->intervals_per_slot > 0) + if (!client->debug_disable_fork_choice_time + && store->has_anchor + && store->intervals_per_slot > 0) { *out_slot = store->time_intervals / store->intervals_per_slot; return true; From 087ad8bf410ca6a949d12af6714fb3424d5e53f1 Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:12:08 +1000 Subject: [PATCH 3/3] Update client_sync_votes.c --- src/core/client_sync_votes.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/core/client_sync_votes.c b/src/core/client_sync_votes.c index 7139ee4..3d992c2 100644 --- a/src/core/client_sync_votes.c +++ b/src/core/client_sync_votes.c @@ -433,16 +433,10 @@ static bool process_vote_locked( lantern_log_debug( "gossip", meta, - "dropping vote validator=%" PRIu64 " slot=%" PRIu64 " (missing target state root=%s)", + "missing target state root=%s for validator=%" PRIu64 " slot=%" PRIu64 " (using current state)", + target_hex[0] ? target_hex : "0x0", vote->data.validator_id, - vote->data.slot, - target_hex[0] ? target_hex : "0x0"); - lantern_vote_rejection_set( - rejection, - "missing target state root=%s", - target_hex[0] ? target_hex : "0x0"); - lantern_state_reset(&target_state); - return false; + vote->data.slot); } if (!lantern_client_verify_vote_signature(