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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions include/lantern/consensus/fork_choice.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ int lantern_fork_choice_update_checkpoints(
const LanternCheckpoint *latest_justified,
const LanternCheckpoint *latest_finalized);

/**
* Restore fork-choice checkpoints from persisted state.
*
* Unlike lantern_fork_choice_update_checkpoints(), this API is intended for
* startup restoration and may move checkpoints backwards when the persisted
* state is behind the temporary anchor checkpoints used during init.
*
* Any provided checkpoint root must already exist in the fork-choice store.
*/
int lantern_fork_choice_restore_checkpoints(
LanternForkChoice *store,
const LanternCheckpoint *latest_justified,
const LanternCheckpoint *latest_finalized);

int lantern_fork_choice_accept_new_votes(LanternForkChoice *store);
int lantern_fork_choice_update_safe_target(LanternForkChoice *store);
int lantern_fork_choice_recompute_head(LanternForkChoice *store);
Expand Down
50 changes: 50 additions & 0 deletions src/consensus/fork_choice.c
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,56 @@ int lantern_fork_choice_update_checkpoints(
return update_global_checkpoints(store, latest_justified, latest_finalized);
}

int lantern_fork_choice_restore_checkpoints(
LanternForkChoice *store,
const LanternCheckpoint *latest_justified,
const LanternCheckpoint *latest_finalized) {
if (!store || !store->initialized || !store->has_anchor) {
return -1;
}

LanternCheckpoint restored_justified = store->latest_justified;
LanternCheckpoint restored_finalized = store->latest_finalized;
bool justified_changed = false;

if (latest_justified && !root_is_zero(&latest_justified->root)) {
size_t justified_index = 0;
if (!map_lookup(store, &latest_justified->root, &justified_index)) {
return -1;
}
restored_justified = *latest_justified;
justified_changed = true;
}
if (latest_finalized && !root_is_zero(&latest_finalized->root)) {
size_t finalized_index = 0;
if (!map_lookup(store, &latest_finalized->root, &finalized_index)) {
return -1;
}
restored_finalized = *latest_finalized;
}
if (restored_finalized.slot > restored_justified.slot) {
return -1;
}

LanternCheckpoint previous_justified = store->latest_justified;
LanternCheckpoint previous_finalized = store->latest_finalized;
LanternRoot previous_head = store->head;
bool previous_has_head = store->has_head;

store->latest_justified = restored_justified;
if (justified_changed && lantern_fork_choice_recompute_head(store) != 0) {
store->latest_justified = previous_justified;
store->latest_finalized = previous_finalized;
store->head = previous_head;
store->has_head = previous_has_head;
return -1;
}

store->latest_justified = restored_justified;
store->latest_finalized = restored_finalized;
return 0;
}

static int find_start_index(
const LanternForkChoice *store,
const LanternRoot *start_root,
Expand Down
2 changes: 1 addition & 1 deletion src/core/client_reqresp_blocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ static int schedule_blocks_request_batch(
{
return LANTERN_CLIENT_ERR_INVALID_PARAM;
}
if (root_count > LANTERN_MAX_BLOCKS_PER_REQUEST)
if (root_count > LANTERN_MAX_REQUEST_BLOCKS)
{
return LANTERN_CLIENT_ERR_INVALID_PARAM;
}
Expand Down
28 changes: 23 additions & 5 deletions src/core/client_sync.c
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,14 @@ int initialize_fork_choice(struct lantern_client *client)
anchor_checkpoint.root = anchor_root;
anchor_checkpoint.slot = anchor.slot;

/*
* Seed fork-choice with anchor_checkpoint (whose root matches the anchor
* block that set_anchor registers in the tree). This guarantees
* lmd_ghost_compute can always find its start_root during block restore.
*
* Real persisted checkpoints are synced to the store AFTER
* restore_persisted_blocks() via lantern_fork_choice_restore_checkpoints().
*/
if (lantern_fork_choice_set_anchor(
&client->fork_choice,
&anchor,
Expand Down Expand Up @@ -651,7 +659,6 @@ static int compare_blocks_by_slot(const void *lhs_ptr, const void *rhs_ptr)
return memcmp(lhs->root.bytes, rhs->root.bytes, LANTERN_ROOT_SIZE);
}


/**
* Restore persisted blocks from storage into fork choice.
*
Expand Down Expand Up @@ -734,6 +741,17 @@ int restore_persisted_blocks(struct lantern_client *client)
&(const struct lantern_log_metadata){.validator = client->node_id},
"advancing fork choice time after restore failed");
}
if (lantern_fork_choice_restore_checkpoints(
&client->fork_choice,
&client->state.latest_justified,
&client->state.latest_finalized)
!= 0)
{
lantern_log_warn(
"forkchoice",
&(const struct lantern_log_metadata){.validator = client->node_id},
"restoring persisted checkpoints after block restore failed");
}

persisted_block_list_reset(&list);
return LANTERN_CLIENT_OK;
Expand Down Expand Up @@ -1238,7 +1256,7 @@ static bool try_schedule_blocks_request_batch(
{
return false;
}
if (root_count > LANTERN_MAX_BLOCKS_PER_REQUEST)
if (root_count > LANTERN_MAX_REQUEST_BLOCKS)
{
return false;
}
Expand Down Expand Up @@ -1898,8 +1916,8 @@ void lantern_client_request_pending_parent_after_blocks(
pending_parent_candidate_compare);
}

LanternRoot request_roots[LANTERN_MAX_BLOCKS_PER_REQUEST];
uint32_t request_depths[LANTERN_MAX_BLOCKS_PER_REQUEST];
LanternRoot request_roots[LANTERN_MAX_REQUEST_BLOCKS];
uint32_t request_depths[LANTERN_MAX_REQUEST_BLOCKS];
size_t request_count = 0;

if (prefer_requested_root)
Expand All @@ -1916,7 +1934,7 @@ void lantern_client_request_pending_parent_after_blocks(

for (size_t i = 0; i < candidate_count; ++i)
{
if (request_count >= LANTERN_MAX_BLOCKS_PER_REQUEST)
if (request_count >= LANTERN_MAX_REQUEST_BLOCKS)
{
break;
}
Expand Down
21 changes: 18 additions & 3 deletions src/core/client_sync_blocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,21 @@ static void adopt_state_locked(struct lantern_client *client, LanternState *stat
LanternState previous = client->state;
client->state = *state;
lantern_state_attach_fork_choice(&client->state, &client->fork_choice);
if (client->has_fork_choice)
{
if (lantern_fork_choice_update_checkpoints(
&client->fork_choice,
&client->state.latest_justified,
&client->state.latest_finalized)
!= 0)
{
lantern_log_warn(
"forkchoice",
&(const struct lantern_log_metadata){.validator = client->node_id},
"failed to sync fork choice checkpoints when adopting state slot=%" PRIu64,
client->state.slot);
}
}
lantern_state_reset(&previous);
}

Expand Down Expand Up @@ -1819,15 +1834,15 @@ bool lantern_client_import_block(
bool have_replay_state = false;
bool processed = false;
bool deferred = false;
LanternRoot missing_roots[LANTERN_MAX_BLOCKS_PER_REQUEST];
LanternRoot missing_roots[LANTERN_MAX_REQUEST_BLOCKS];
size_t missing_count = 0;

if (rebuild_state_for_root_locked(
client,
&parent_root,
&replay_state,
missing_roots,
LANTERN_MAX_BLOCKS_PER_REQUEST,
LANTERN_MAX_REQUEST_BLOCKS,
&missing_count))
{
have_replay_state = true;
Expand Down Expand Up @@ -1869,7 +1884,7 @@ bool lantern_client_import_block(
true);
if (missing_count > 0)
{
uint32_t request_depths[LANTERN_MAX_BLOCKS_PER_REQUEST];
uint32_t request_depths[LANTERN_MAX_REQUEST_BLOCKS];
uint32_t request_depth = backfill_depth + 1u;
if (request_depth > LANTERN_MAX_BACKFILL_DEPTH)
{
Expand Down
2 changes: 0 additions & 2 deletions src/core/client_sync_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ typedef struct peer_id peer_id_t;
* Constants
* ============================================================================ */

/** Maximum roots per blocks_by_root request */
#define LANTERN_MAX_BLOCKS_PER_REQUEST 10u
/**
* Maximum parent depth for ancestor backfill requests.
*
Expand Down
82 changes: 82 additions & 0 deletions tests/unit/test_fork_choice.c
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,85 @@ static int test_fork_choice_checkpoint_progression(void) {
return 0;
}

static int test_fork_choice_restore_checkpoints(void) {
LanternForkChoice store;
lantern_fork_choice_init(&store);

LanternConfig config = {.num_validators = 4, .genesis_time = 1};
assert(lantern_fork_choice_configure(&store, &config) == 0);

LanternBlock genesis;
init_block(&genesis, 0, 0, NULL, 0x41);
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 block_one;
init_block(&block_one, 1, 0, &genesis_root, 0x42);
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);

LanternBlock block_two;
init_block(&block_two, 2, 1, &block_one_root, 0x43);
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);
assert(
lantern_fork_choice_add_block(
&store,
&block_two,
NULL,
NULL,
NULL,
&block_two_root)
== 0);

assert(lantern_fork_choice_update_checkpoints(&store, &block_two_cp, &block_one_cp) == 0);
const LanternCheckpoint *latest_justified = lantern_fork_choice_latest_justified(&store);
const LanternCheckpoint *latest_finalized = lantern_fork_choice_latest_finalized(&store);
assert(latest_justified && checkpoints_equal(latest_justified, &block_two_cp));
assert(latest_finalized && checkpoints_equal(latest_finalized, &block_one_cp));

assert(lantern_fork_choice_restore_checkpoints(&store, &block_one_cp, &genesis_cp) == 0);
latest_justified = lantern_fork_choice_latest_justified(&store);
latest_finalized = lantern_fork_choice_latest_finalized(&store);
assert(latest_justified && checkpoints_equal(latest_justified, &block_one_cp));
assert(latest_finalized && checkpoints_equal(latest_finalized, &genesis_cp));

LanternRoot head_before_failure;
assert(lantern_fork_choice_current_head(&store, &head_before_failure) == 0);

LanternCheckpoint unknown_cp = block_one_cp;
fill_root(&unknown_cp.root, 0xEE);
assert(lantern_fork_choice_restore_checkpoints(&store, &unknown_cp, &genesis_cp) != 0);

const LanternCheckpoint *justified_after_failure = lantern_fork_choice_latest_justified(&store);
const LanternCheckpoint *finalized_after_failure = lantern_fork_choice_latest_finalized(&store);
assert(justified_after_failure && checkpoints_equal(justified_after_failure, &block_one_cp));
assert(finalized_after_failure && checkpoints_equal(finalized_after_failure, &genesis_cp));

LanternRoot head_after_failure;
assert(lantern_fork_choice_current_head(&store, &head_after_failure) == 0);
assert(roots_equal(&head_after_failure, &head_before_failure));

lantern_fork_choice_reset(&store);
reset_block(&block_two);
reset_block(&block_one);
reset_block(&genesis);
return 0;
}

static int test_fork_choice_advance_time_schedules_votes(void) {
LanternForkChoice store;
lantern_fork_choice_init(&store);
Expand Down Expand Up @@ -581,6 +660,9 @@ int main(void) {
if (test_fork_choice_checkpoint_progression() != 0) {
return 1;
}
if (test_fork_choice_restore_checkpoints() != 0) {
return 1;
}
if (test_fork_choice_advance_time_schedules_votes() != 0) {
return 1;
}
Expand Down
Loading