Skip to content

Commit 4b4eba4

Browse files
committed
refactor(fork-choice): tighten invariants and clean up minor issues
- Use positive condition `fork.lt(.gloas)` instead of `!fork.gte(.gloas)` - Replace GenesisBlockNotAvailable error with assert (invariant) - Propagate error from getJustifiedBlock in getSafeExecutionBlockHash - Extract AncestorAndNonAncestorBlocks named type for clarity - Force-unwrap parent FULL status (guaranteed by getParentPayloadStatus)
1 parent 40cad05 commit 4b4eba4

2 files changed

Lines changed: 19 additions & 8 deletions

File tree

src/fork_choice/fork_choice.zig

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ pub const ForkChoiceError = ProtoArrayError || error{
9090
ForkChoiceStoreErr,
9191
UnableToSetJustifiedCheckpoint,
9292
AfterBlockFailed,
93-
GenesisBlockNotAvailable,
9493
DependentRootNotFound,
9594
};
9695

@@ -118,6 +117,13 @@ pub const AncestorStatus = enum {
118117
};
119118

120119
/// Result of `getCommonAncestorDepth`: ancestor status + optional depth.
120+
/// Result of `getAllAncestorAndNonAncestorBlocks`: owned slices of ancestor
121+
/// and non-ancestor proto-blocks partitioned by the finalized checkpoint.
122+
pub const AncestorAndNonAncestorBlocks = struct {
123+
ancestors: []ProtoBlock,
124+
non_ancestors: []ProtoBlock,
125+
};
126+
121127
pub const AncestorResult = union(AncestorStatus) {
122128
common_ancestor: struct { depth: u32 },
123129
descendant: void,
@@ -1559,7 +1565,7 @@ pub const ForkChoice = struct {
15591565

15601566
/// Propagate execution layer validity response through the DAG.
15611567
/// Only sets irrecoverable_error for InvalidLVHExecutionResponse;
1562-
/// other errors are silently ignored (matching TS behavior).
1568+
/// other errors are silently ignored.
15631569
pub fn validateLatestHash(
15641570
self: *ForkChoice,
15651571
allocator: Allocator,
@@ -1641,8 +1647,8 @@ pub const ForkChoice = struct {
16411647
/// no fork-check is performed inside this function.
16421648
///
16431649
/// Spec: https://github.com/ethereum/consensus-specs/blob/v1.6.0/fork_choice/safe-block.md#get_safe_execution_block_hash
1644-
pub fn getSafeExecutionBlockHash(self: *const ForkChoice) Root {
1645-
const justified_block = self.getJustifiedBlock() catch return ZERO_HASH;
1650+
pub fn getSafeExecutionBlockHash(self: *const ForkChoice) !Root {
1651+
const justified_block = try self.getJustifiedBlock();
16461652
return justified_block.extra_meta.executionPayloadBlockHash() orelse ZERO_HASH;
16471653
}
16481654

@@ -1760,7 +1766,7 @@ pub const ForkChoice = struct {
17601766
allocator: Allocator,
17611767
block_root: Root,
17621768
status: PayloadStatus,
1763-
) !struct { ancestors: []ProtoBlock, non_ancestors: []ProtoBlock } {
1769+
) !AncestorAndNonAncestorBlocks {
17641770
var pa_result = try self.proto_array.getAllAncestorAndNonAncestorNodes(allocator, block_root, status);
17651771
// The last ancestor block is the previous finalized one, exclude it.
17661772
if (pa_result.ancestors.items.len > 0) _ = pa_result.ancestors.pop();
@@ -1801,9 +1807,11 @@ pub const ForkChoice = struct {
18011807
@as(i64, @intCast(epoch_diff_val * preset.SLOTS_PER_EPOCH));
18021808

18031809
// Special case close to genesis block, return the genesis block root.
1810+
// Invariant: when before_slot <= 0, genesis has not been pruned yet
1811+
// (finalization hasn't advanced enough), so items[0] is always genesis.
18041812
if (before_slot_signed <= 0) {
18051813
const genesis_block = &self.proto_array.nodes.items[0];
1806-
if (genesis_block.slot != 0) return error.GenesisBlockNotAvailable;
1814+
assert(genesis_block.slot == 0);
18071815
return genesis_block.block_root;
18081816
}
18091817
const before_slot: Slot = @intCast(before_slot_signed);
@@ -2027,7 +2035,7 @@ fn getCheckpointPayloadStatus(state: *CachedBeaconState, checkpoint_epoch: Epoch
20272035
const fork = state.config.forkSeq(checkpoint_slot);
20282036

20292037
// Pre-Gloas: always FULL.
2030-
if (!fork.gte(.gloas)) return .full;
2038+
if (fork.lt(.gloas)) return .full;
20312039

20322040
// For Gloas, check state.execution_payload_availability.
20332041
const payload_available = state.state.executionPayloadAvailability(

src/fork_choice/proto_array.zig

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,10 @@ pub const ProtoArray = struct {
687687
.gloas => |g| blk: {
688688
const parent_status = try self.getParentPayloadStatus(block.parent_root, block.parent_block_hash);
689689
break :blk switch (parent_status) {
690-
.full => g.full,
690+
// If getParentPayloadStatus returns .full, the FULL variant
691+
// must exist: getNodeByRootAndBlockHash only matches a FULL
692+
// node when g.full is set (written by onExecutionPayload).
693+
.full => g.full.?,
691694
.empty => g.empty,
692695
.pending => g.pending,
693696
};

0 commit comments

Comments
 (0)