From 8c9b53502fa42671226abe6038368b1c0c15cb49 Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:05:15 +1000 Subject: [PATCH] Use updated finalized slot in attestations --- src/consensus/state.c | 28 +++--- tests/unit/test_state.c | 203 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 208 insertions(+), 23 deletions(-) diff --git a/src/consensus/state.c b/src/consensus/state.c index 18fcf52..a8e4c86 100644 --- a/src/consensus/state.c +++ b/src/consensus/state.c @@ -1716,7 +1716,7 @@ static int lantern_state_process_attestations_internal( LanternCheckpoint latest_justified = state->latest_justified; LanternCheckpoint latest_finalized = state->latest_finalized; - uint64_t base_finalized_slot = latest_finalized.slot; + uint64_t finalized_slot = latest_finalized.slot; double att_batch_start = lantern_time_now_seconds(); size_t att_attempted = 0; bool finalization_attempted = false; @@ -1891,15 +1891,15 @@ static int lantern_state_process_attestations_internal( continue; } - /* Target slot must be justifiable after the pre-loop finalized slot snapshot (leanSpec line 410) */ - if (!lantern_slot_is_justifiable(vote->target.slot, base_finalized_slot)) { + /* Target slot must remain justifiable after the current finalized slot. */ + if (!lantern_slot_is_justifiable(vote->target.slot, finalized_slot)) { if (trace_finalization) { lantern_log_debug( "state", &meta, "finalization trace skip non-justifiable target_slot=%" PRIu64 " finalized=%" PRIu64, vote->target.slot, - base_finalized_slot); + finalized_slot); } record_attestation_validation_metric(att_validation_start, true); continue; @@ -1991,14 +1991,11 @@ static int lantern_state_process_attestations_internal( vote->target.slot); } - /* Finalization: if the target is the next valid justifiable slot after source (leanSpec lines 435-439) - * - * The gap check is evaluated relative to the pre-loop finalized slot snapshot. - * leanSpec uses self.latest_finalized.slot from the immutable input state for this check, - * so use base_finalized_slot rather than the progressively updated latest_finalized.slot. + /* Finalization: if the target is the next valid justifiable slot after source + * relative to the current finalized boundary, finalize the source checkpoint. */ bool has_justifiable_between = has_justifiable_slot_between( - vote->source.slot, vote->target.slot, base_finalized_slot); + vote->source.slot, vote->target.slot, finalized_slot); bool vote_has_consecutive_source = !has_justifiable_between; if (trace_finalization) { @@ -2016,12 +2013,13 @@ static int lantern_state_process_attestations_internal( if (vote_has_consecutive_source) { /* Finalize the source checkpoint */ - uint64_t old_finalized_slot = latest_finalized.slot; + uint64_t old_finalized_slot = finalized_slot; latest_finalized = vote->source; + finalized_slot = latest_finalized.slot; finalization_attempted = true; lean_metrics_record_finalization_attempt(true); - if (latest_finalized.slot > old_finalized_slot) { - uint64_t delta = latest_finalized.slot - old_finalized_slot; + if (finalized_slot > old_finalized_slot) { + uint64_t delta = finalized_slot - old_finalized_slot; if (delta > SIZE_MAX) { if (finalization_attempted) { lean_metrics_record_finalization_attempt(false); @@ -2041,8 +2039,8 @@ static int lantern_state_process_attestations_internal( state->latest_finalized = latest_finalized; if (lantern_state_prune_justification_roots( state, - base_finalized_slot, - latest_finalized.slot, + old_finalized_slot, + finalized_slot, validator_count, &meta) != 0) { diff --git a/tests/unit/test_state.c b/tests/unit/test_state.c index bab35b9..2979b77 100644 --- a/tests/unit/test_state.c +++ b/tests/unit/test_state.c @@ -1175,6 +1175,187 @@ static int test_attestations_finalize_across_gap(void) { return 0; } +static int test_attestations_use_updated_finalized_slot_for_target_justifiability(void) { + LanternState state; + lantern_state_init(&state); + expect_zero( + lantern_state_generate_genesis(&state, 748, 3), + "genesis for updated finalized target-justifiability test"); + + populate_historical_hashes_for_tests(&state, 9); + + state.latest_finalized.slot = 0; + state.latest_finalized.root = get_historical_root_for_tests(&state, 0); + state.latest_justified.slot = 1; + state.latest_justified.root = get_historical_root_for_tests(&state, 1); + mark_slot_justified_for_tests(&state, 1); + + LanternCheckpoint first_source = state.latest_justified; + LanternCheckpoint first_target = first_source; + first_target.slot = 2; + first_target.root = get_historical_root_for_tests(&state, first_target.slot); + + LanternCheckpoint second_source = first_target; + LanternCheckpoint second_target = second_source; + second_target.slot = 9; + second_target.root = get_historical_root_for_tests(&state, second_target.slot); + + LanternAttestations attestations; + lantern_attestations_init(&attestations); + LanternSignatureList signatures; + lantern_signature_list_init(&signatures); + expect_zero( + lantern_attestations_resize(&attestations, 4), + "resize updated finalized target-justifiability attestations"); + expect_zero( + lantern_signature_list_resize(&signatures, 4), + "resize updated finalized target-justifiability signatures"); + + build_vote( + &attestations.data[0], + &signatures.data[0], + 0, + first_target.slot, + &first_source, + &first_target, + 0xA8); + build_vote( + &attestations.data[1], + &signatures.data[1], + 1, + first_target.slot, + &first_source, + &first_target, + 0xA9); + build_vote( + &attestations.data[2], + &signatures.data[2], + 0, + second_target.slot, + &second_source, + &second_target, + 0xAA); + build_vote( + &attestations.data[3], + &signatures.data[3], + 1, + second_target.slot, + &second_source, + &second_target, + 0xAB); + + expect_zero( + lantern_state_process_attestations(&state, &attestations, &signatures), + "process updated finalized target-justifiability attestations"); + + assert(state.latest_finalized.slot == first_source.slot); + assert(state.latest_justified.slot == first_target.slot); + assert(state.justification_roots.length == 0); + + bool target_justified = true; + expect_zero( + lantern_state_get_justified_slot_bit(&state, second_target.slot, &target_justified), + "read skipped target justification bit"); + assert(!target_justified); + + lantern_attestations_reset(&attestations); + lantern_signature_list_reset(&signatures); + lantern_state_reset(&state); + return 0; +} + +static int test_attestations_use_updated_finalized_slot_for_gap_check(void) { + LanternState state; + lantern_state_init(&state); + expect_zero( + lantern_state_generate_genesis(&state, 749, 3), + "genesis for updated finalized gap-check test"); + + populate_historical_hashes_for_tests(&state, 9); + + state.latest_finalized.slot = 0; + state.latest_finalized.root = get_historical_root_for_tests(&state, 0); + state.latest_justified.slot = 3; + state.latest_justified.root = get_historical_root_for_tests(&state, 3); + + expect_zero( + lantern_bitlist_resize(&state.justified_slots, 6), + "resize justified slots for updated finalized gap-check test"); + assert(state.justified_slots.bytes != NULL); + memset(state.justified_slots.bytes, 0, state.justified_slots.capacity); + mark_slot_justified_for_tests(&state, 3); + mark_slot_justified_for_tests(&state, 6); + + LanternCheckpoint first_source = state.latest_justified; + LanternCheckpoint first_target = first_source; + first_target.slot = 4; + first_target.root = get_historical_root_for_tests(&state, first_target.slot); + + LanternCheckpoint second_source = first_source; + second_source.slot = 6; + second_source.root = get_historical_root_for_tests(&state, second_source.slot); + + LanternCheckpoint second_target = second_source; + second_target.slot = 9; + second_target.root = get_historical_root_for_tests(&state, second_target.slot); + + LanternAttestations attestations; + lantern_attestations_init(&attestations); + LanternSignatureList signatures; + lantern_signature_list_init(&signatures); + expect_zero( + lantern_attestations_resize(&attestations, 4), + "resize updated finalized gap-check attestations"); + expect_zero( + lantern_signature_list_resize(&signatures, 4), + "resize updated finalized gap-check signatures"); + + build_vote( + &attestations.data[0], + &signatures.data[0], + 0, + first_target.slot, + &first_source, + &first_target, + 0xAC); + build_vote( + &attestations.data[1], + &signatures.data[1], + 1, + first_target.slot, + &first_source, + &first_target, + 0xAD); + build_vote( + &attestations.data[2], + &signatures.data[2], + 0, + second_target.slot, + &second_source, + &second_target, + 0xAE); + build_vote( + &attestations.data[3], + &signatures.data[3], + 1, + second_target.slot, + &second_source, + &second_target, + 0xAF); + + expect_zero( + lantern_state_process_attestations(&state, &attestations, &signatures), + "process updated finalized gap-check attestations"); + + assert(state.latest_finalized.slot == first_source.slot); + assert(state.latest_justified.slot == second_target.slot); + + lantern_attestations_reset(&attestations); + lantern_signature_list_reset(&signatures); + lantern_state_reset(&state); + return 0; +} + static int test_pruning_keeps_pending_justifications(void) { LanternState state; lantern_state_init(&state); @@ -1270,7 +1451,7 @@ static int test_pending_votes_survive_interleaved_justification_and_finalization const uint64_t finalized_slot = 334u; const uint64_t justified_slot = 340u; const uint64_t target_justified_slot = 343u; - const uint64_t target_pending_slot = 350u; + const uint64_t target_pending_slot = 346u; const uint64_t block_slot = 354u; const uint64_t anchor = finalized_slot + 1u; const size_t validator_count_sz = (size_t)validator_count; @@ -1313,7 +1494,7 @@ static int test_pending_votes_survive_interleaved_justification_and_finalization /* Pre-slot-354 pending votes: * root@343 -> validators {0,2,4} - * root@350 -> validators {0,3} + * root@346 -> validators {0,3} */ expect_zero( lantern_bitlist_set(&state.justification_validators, 0, true), @@ -1326,10 +1507,10 @@ static int test_pending_votes_survive_interleaved_justification_and_finalization "seed root@343 vote for validator 4"); expect_zero( lantern_bitlist_set(&state.justification_validators, validator_count_sz + 0u, true), - "seed root@350 vote for validator 0"); + "seed root@346 vote for validator 0"); expect_zero( lantern_bitlist_set(&state.justification_validators, validator_count_sz + 3u, true), - "seed root@350 vote for validator 3"); + "seed root@346 vote for validator 3"); LanternAttestations attestations; lantern_attestations_init(&attestations); @@ -1423,7 +1604,7 @@ static int test_pending_votes_preserved_when_new_root_inserts_before_existing_ro const uint64_t finalized_slot = 334u; const uint64_t justified_slot = 340u; const uint64_t target_justified_slot = 343u; - const uint64_t target_pending_slot = 350u; + const uint64_t target_pending_slot = 346u; const uint64_t anchor = finalized_slot + 1u; const size_t validator_count_sz = (size_t)validator_count; @@ -1456,7 +1637,7 @@ static int test_pending_votes_preserved_when_new_root_inserts_before_existing_ro LanternSignatureList signatures; lantern_signature_list_init(&signatures); - /* Step 1: create pending root@350 with votes {0,3}. */ + /* Step 1: create pending root@346 with votes {0,3}. */ expect_zero(lantern_attestations_resize(&attestations, 2), "resize step1 attestations"); expect_zero(lantern_signature_list_resize(&signatures, 2), "resize step1 signatures"); build_vote( @@ -1479,7 +1660,7 @@ static int test_pending_votes_preserved_when_new_root_inserts_before_existing_ro lantern_state_process_attestations(&state, &attestations, &signatures), "process insertion-order step1"); - /* Step 2: add root@343 with votes {0,2,4}. This must insert before root@350. */ + /* Step 2: add root@343 with votes {0,2,4}. This must insert before root@346. */ expect_zero(lantern_attestations_resize(&attestations, 3), "resize step2 attestations"); expect_zero(lantern_signature_list_resize(&signatures, 3), "resize step2 signatures"); build_vote( @@ -1510,7 +1691,7 @@ static int test_pending_votes_preserved_when_new_root_inserts_before_existing_ro lantern_state_process_attestations(&state, &attestations, &signatures), "process insertion-order step2"); - /* Step 3: justify root@343 and add one more vote to root@350. */ + /* Step 3: justify root@343 and add one more vote to root@346. */ expect_zero(lantern_attestations_resize(&attestations, 2), "resize step3 attestations"); expect_zero(lantern_signature_list_resize(&signatures, 2), "resize step3 signatures"); build_vote( @@ -3237,6 +3418,12 @@ int main(void) { if (test_attestations_finalize_across_gap() != 0) { return 1; } + if (test_attestations_use_updated_finalized_slot_for_target_justifiability() != 0) { + return 1; + } + if (test_attestations_use_updated_finalized_slot_for_gap_check() != 0) { + return 1; + } if (test_pruning_keeps_pending_justifications() != 0) { return 1; }