mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-11 18:04:18 +00:00
Fix HTTP state API bug and add --epochs-per-migration (#4236)
## Issue Addressed
Fix an issue observed by `@zlan` on Discord where Lighthouse would sometimes return this error when looking up states via the API:
> {"code":500,"message":"UNHANDLED_ERROR: ForkChoiceError(MissingProtoArrayBlock(0xc9cf1495421b6ef3215d82253b388d77321176a1dcef0db0e71a0cd0ffc8cdb7))","stacktraces":[]}
## Proposed Changes
The error stems from a faulty assumption in the HTTP API logic: that any state in the hot database must have its block in fork choice. This isn't true because the state's hot database may update much less frequently than the fork choice store, e.g. if reconstructing states (where freezer migration pauses), or if the freezer migration runs slowly. There could also be a race between loading the hot state and checking fork choice, e.g. even if the finalization migration of DB+fork choice were atomic, the update could happen between the 1st and 2nd calls.
To address this I've changed the HTTP API logic to use the finalized block's execution status as a fallback where it is safe to do so. In the case where a block is non-canonical and prior to finalization (permanently orphaned) we default `execution_optimistic` to `true`.
## Additional Info
I've also added a new CLI flag to reduce the frequency of the finalization migration as this is useful for several purposes:
- Spacing out database writes (less frequent, larger batches)
- Keeping a limited chain history with high availability, e.g. the last month in the hot database.
This new flag made it _substantially_ easier to test this change. It was extracted from `tree-states` (where it's called `--db-migration-period`), which is why this PR also carries the `tree-states` label.
This commit is contained in:
@@ -70,15 +70,32 @@ impl StateId {
|
||||
.map_err(BeaconChainError::DBError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||
{
|
||||
let execution_optimistic = chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.is_optimistic_or_invalid_block_no_fallback(&hot_summary.latest_block_root)
|
||||
.map_err(BeaconChainError::ForkChoiceError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
let finalized = chain
|
||||
.is_finalized_state(root, hot_summary.slot)
|
||||
let finalization_status = chain
|
||||
.state_finalization_and_canonicity(root, hot_summary.slot)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
let finalized = finalization_status.is_finalized();
|
||||
let fork_choice = chain.canonical_head.fork_choice_read_lock();
|
||||
let execution_optimistic = if finalization_status.slot_is_finalized
|
||||
&& !finalization_status.canonical
|
||||
{
|
||||
// This block is permanently orphaned and has likely been pruned from fork
|
||||
// choice. If it isn't found in fork choice, mark it optimistic to be on the
|
||||
// safe side.
|
||||
fork_choice
|
||||
.is_optimistic_or_invalid_block_no_fallback(
|
||||
&hot_summary.latest_block_root,
|
||||
)
|
||||
.unwrap_or(true)
|
||||
} else {
|
||||
// This block is either old and finalized, or recent and unfinalized, so
|
||||
// it's safe to fallback to the optimistic status of the finalized block.
|
||||
chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.is_optimistic_or_invalid_block(&hot_summary.latest_block_root)
|
||||
.map_err(BeaconChainError::ForkChoiceError)
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?
|
||||
};
|
||||
return Ok((*root, execution_optimistic, finalized));
|
||||
} else if let Some(_cold_state_slot) = chain
|
||||
.store
|
||||
|
||||
Reference in New Issue
Block a user