Skip to content

Weber Fork Choice Rule

Notice: This document is a work-in-progress for researchers and implementers of the Weber protocol.

Table of contents

Introduction

The fork choice rule is the mechanism by which nodes in the Weber network reach consensus about the canonical chain. Weber builds upon the latest message driven Greedy Heaviest Observed SubTree (LMD-GHOST) algorithm but introduces significant enhancements:

  1. Reputation-weighted voting: Attestations from validators with higher reputation scores carry more weight
  2. Optimistic fork choice acceleration: A mechanism to quickly converge on a head without waiting for full attestation collection
  3. Multi-factor scoring: Block scoring based on multiple factors beyond simple vote counting

This document specifies the enhanced fork choice algorithm, including the procedures for block scoring, vote accounting, and chain selection.

Preliminaries

Type definitions

# Validator index
ValidatorIndex = uint64

# Epoch number
Epoch = uint64

# Slot number
Slot = uint64

# A root/hash of a beacon block
Root = bytes32

# Validator reputation score (0-1000)
ReputationScore = uint16

# Boolean quick finality bit
QuickFinalityBit = bool

# Checkpoint for justification/finalization
class Checkpoint:
    epoch: Epoch
    root: Root

# Store object to track fork choice state
class Store:
    # Time
    time: uint64
    genesis_time: uint64

    # Blocks
    blocks: Dict[Root, BeaconBlock]
    block_states: Dict[Root, BeaconState]

    # Checkpoints
    justified_checkpoint: Checkpoint
    finalized_checkpoint: Checkpoint

    # Best justified checkpoint seen
    best_justified_checkpoint: Checkpoint

    # Weber-specific additions
    validator_reputations: Dict[ValidatorIndex, ReputationScore]
    validator_latest_messages: Dict[ValidatorIndex, LatestMessage]
    optimistic_checkpoints: List[Checkpoint]
    block_scores: Dict[Root, float]
    quick_finality_votes: Dict[Root, Set[ValidatorIndex]]

    # Cache for vote weights
    block_vote_weights: Dict[Root, float]

    # Online status tracking
    validator_online_status: Dict[ValidatorIndex, bool]

    # Last attestation time for each validator
    validator_last_attestation: Dict[ValidatorIndex, uint64]

    # Chain health metrics
    chain_health: ChainHealthMetrics
1
2
3
class LatestMessage:
    epoch: Epoch
    root: Root
1
2
3
4
5
6
class ChainHealthMetrics:
    participation_rate: float
    reorg_count: uint64
    attestation_inclusion_time: float
    last_finalized_epoch: Epoch
    finality_distance: uint64

Algorithm constants

# Weber-specific constants for fork choice
REPUTATION_WEIGHT_FACTOR = 0.5  # How much reputation affects vote weight (0-1)
OPTIMISTIC_FACTOR = 0.2  # Weight given to optimistic votes
QUICK_FINALITY_THRESHOLD = 3  # Number of quick finality votes needed
MIN_REPUTATION_FOR_QUICK_FINALITY = 800  # Minimum reputation score to submit quick finality votes
QUICK_FINALITY_WEIGHT = 2.0  # Weight multiplier for quick finality votes
MAX_OPTIMISTIC_BOOST = 0.3  # Maximum boost from optimistic voting
VOTE_WEIGHT_SMOOTHING = 0.1  # Smoothing factor for weight calculations
OPTIMISTIC_CHECK_INTERVAL = 32  # Number of slots between optimistic decision checks
BASE_REPUTATION_SCORE = 500  # Starting reputation for new validators
MAX_REPUTATION_SCORE = 1000  # Maximum possible reputation score
MIN_REPUTATION_SCORE = 0  # Minimum possible reputation score
ONLINE_THRESHOLD_SLOTS = 32  # Number of slots before marking a validator offline

Fork choice state

Fork choice information is maintained in a Store object. The Weber fork choice extends the standard Store with additional fields to track validator reputations, optimistic checkpoints, and quick finality votes.

def get_forkchoice_store(state: BeaconState, genesis_time: uint64) -> Store:
    """
    Initialize a fork choice store from the given genesis state and time.
    """
    current_epoch = get_current_epoch(state)
    justified_checkpoint = Checkpoint(
        epoch=current_epoch,
        root=state.current_justified_checkpoint.root
    )
    finalized_checkpoint = Checkpoint(
        epoch=current_epoch,
        root=state.finalized_checkpoint.root
    )

    # Standard store initialization
    store = Store(
        time=uint64(genesis_time),
        genesis_time=uint64(genesis_time),
        justified_checkpoint=justified_checkpoint,
        finalized_checkpoint=finalized_checkpoint,
        best_justified_checkpoint=justified_checkpoint,
        blocks={},
        block_states={},
    )

    # Add genesis block
    genesis_block_root = state.latest_block_header.hash_tree_root()
    store.blocks[genesis_block_root] = BeaconBlock()
    store.block_states[genesis_block_root] = state.copy()

    # Weber-specific initializations
    store.validator_reputations = {
        i: BASE_REPUTATION_SCORE for i in range(len(state.validators))
    }
    store.validator_latest_messages = {}
    store.optimistic_checkpoints = []
    store.block_scores = {}
    store.quick_finality_votes = {}
    store.block_vote_weights = {}
    store.validator_online_status = {
        i: True for i in range(len(state.validators))
    }
    store.validator_last_attestation = {
        i: uint64(genesis_time) for i in range(len(state.validators))
    }
    store.chain_health = ChainHealthMetrics(
        participation_rate=1.0,
        reorg_count=0,
        attestation_inclusion_time=0.0,
        last_finalized_epoch=finalized_checkpoint.epoch,
        finality_distance=0
    )

    return store

Standard LMD-GHOST fork choice rule

The LMD-GHOST (Latest Message Driven Greedy Heaviest Observed SubTree) algorithm is the foundation of Weber's fork choice rule. This section specifies the standard algorithm before Weber's enhancements.

def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
    """
    Get the ancestor of the block with the given `root` at the given `slot`.
    """
    block = store.blocks[root]
    if block.slot > slot:
        return get_ancestor(store, block.parent_root, slot)
    elif block.slot == slot:
        return root
    else:
        # root is older than queried slot, thus a skip slot. Return most recent root for slot.
        return root
def get_latest_attesting_balance(store: Store, root: Root) -> Gwei:
    """
    Return the combined effective balance of all validators that have an attestation
    for the block with the given `root`.
    """
    state = store.block_states[root]
    active_indices = get_active_validator_indices(state, get_current_epoch(state))

    total_balance = Gwei(0)
    for validator_index in active_indices:
        if validator_index in store.validator_latest_messages:
            latest_message = store.validator_latest_messages[validator_index]
            voting_root = latest_message.root
            if get_ancestor(store, voting_root, store.blocks[root].slot) == root:
                total_balance += state.validators[validator_index].effective_balance

    return total_balance
def get_head_standard(store: Store) -> Root:
    """
    Return the head block root according to the standard LMD-GHOST algorithm.
    """
    # Start from justified root
    head = store.justified_checkpoint.root

    # Climb up to current slot
    justified_slot = store.blocks[head].slot
    current_slot = (store.time - store.genesis_time) // SECONDS_PER_SLOT

    while justified_slot < current_slot:
        children = [
            root for root in store.blocks
            if store.blocks[root].parent_root == head
            and store.blocks[root].slot > justified_slot
        ]

        if len(children) == 0:
            # No children, return current head
            return head

        # Find heaviest child
        head = max(children, key=lambda root: get_latest_attesting_balance(store, root))
        justified_slot = store.blocks[head].slot

    return head

Weber reputation-weighted fork choice

Overview

Weber enhances the standard LMD-GHOST algorithm by applying reputation weights to validator votes. This helps mitigate various attack vectors and creates stronger incentives for validators to follow the protocol rules.

Attestation weighting

In Weber, the weight of an attestation depends on the validator's reputation score:

def get_validator_weight(store: Store, validator_index: ValidatorIndex) -> float:
    """
    Calculate the voting weight of a validator based on reputation.
    """
    if validator_index not in store.validator_reputations:
        return 1.0  # Default weight

    reputation = store.validator_reputations[validator_index]

    # Apply non-linear weighting function to reputation
    # This makes high reputation more valuable while ensuring some baseline weight
    base_weight = 1.0
    reputation_weight = (reputation / MAX_REPUTATION_SCORE) ** 2  # Squared for steeper curve

    # Final weight is a combination of base weight and reputation
    final_weight = base_weight * (1.0 - REPUTATION_WEIGHT_FACTOR) + reputation_weight * REPUTATION_WEIGHT_FACTOR

    # Ensure weight is always positive but capped
    return max(0.1, min(final_weight, 2.0))
def update_validator_reputation(store: Store, validator_index: ValidatorIndex, delta: int) -> None:
    """
    Update the reputation score of a validator with the given delta.
    Ensures the score stays within bounds.
    """
    if validator_index not in store.validator_reputations:
        store.validator_reputations[validator_index] = BASE_REPUTATION_SCORE

    current_score = store.validator_reputations[validator_index]
    new_score = current_score + delta

    # Ensure the score stays within bounds
    clamped_score = max(MIN_REPUTATION_SCORE, min(new_score, MAX_REPUTATION_SCORE))
    store.validator_reputations[validator_index] = clamped_score
def update_validator_recent_activity(store: Store, validator_index: ValidatorIndex) -> None:
    """
    Update the record of a validator's most recent activity.
    Used to track online status.
    """
    store.validator_last_attestation[validator_index] = store.time

    # Mark validator as online if they were offline
    if not store.validator_online_status.get(validator_index, True):
        store.validator_online_status[validator_index] = True
def update_validator_online_status(store: Store) -> None:
    """
    Update the online status of all validators based on recent activity.
    """
    current_time = store.time
    for validator_index in store.validator_last_attestation:
        last_attestation_time = store.validator_last_attestation[validator_index]
        offline_threshold = ONLINE_THRESHOLD_SLOTS * SECONDS_PER_SLOT

        if current_time - last_attestation_time > offline_threshold:
            store.validator_online_status[validator_index] = False

Vote accounting

Weber tracks votes with reputation-based weighting:

def process_attestation_with_weight(store: Store, attestation: Attestation) -> None:
    """
    Process an attestation and update the store with weighted votes.
    """
    target = attestation.data.target
    beacon_block_root = attestation.data.beacon_block_root

    # Get attesting indices
    attesting_indices = get_attesting_indices(attestation)

    for validator_index in attesting_indices:
        # Skip inactive validators
        if not is_active_validator(store, validator_index):
            continue

        # Update latest message
        store.validator_latest_messages[validator_index] = LatestMessage(
            epoch=target.epoch,
            root=beacon_block_root
        )

        # Update recent activity timestamp
        update_validator_recent_activity(store, validator_index)

        # Reward validators for timely attestation
        slot_delay = get_attestation_delay(store, attestation)
        if slot_delay <= TARGET_ATTESTATION_DELAY:
            update_validator_reputation(store, validator_index, REPUTATION_REWARD_TIMELY_ATTESTATION)

    # Invalidate affected block scores
    invalidate_block_scores_for_attestation(store, attestation)

    # Update chain health metrics
    update_chain_health_metrics(store)

Enhanced fork choice algorithm

Weber's primary fork choice algorithm extends LMD-GHOST with reputation weighting:

def get_latest_attesting_weighted_balance(store: Store, root: Root) -> float:
    """
    Return the reputation-weighted balance of all validators attesting to the block with the given root.
    """
    state = store.block_states[root]
    active_indices = get_active_validator_indices(state, get_current_epoch(state))

    total_weighted_balance = 0.0
    for validator_index in active_indices:
        if validator_index in store.validator_latest_messages:
            latest_message = store.validator_latest_messages[validator_index]
            voting_root = latest_message.root

            if get_ancestor(store, voting_root, store.blocks[root].slot) == root:
                # Apply validator-specific weight
                weight = get_validator_weight(store, validator_index)
                effective_balance = state.validators[validator_index].effective_balance
                total_weighted_balance += effective_balance * weight

    return total_weighted_balance
def compute_block_score(store: Store, root: Root) -> float:
    """
    Compute a comprehensive score for a block, considering:
    1. Weighted attestation balance
    2. Quick finality votes
    3. Block proposer reputation
    """
    # Use cached score if available and recent
    if root in store.block_scores and is_score_recent(store, root):
        return store.block_scores[root]

    # Get attestation weight
    attestation_weight = get_latest_attesting_weighted_balance(store, root)

    # Apply quick finality boost if applicable
    quick_finality_boost = 0.0
    if root in store.quick_finality_votes:
        num_votes = len(store.quick_finality_votes[root])
        if num_votes >= QUICK_FINALITY_THRESHOLD:
            quick_finality_boost = attestation_weight * QUICK_FINALITY_WEIGHT

    # Apply proposer reputation bonus
    proposer_index = store.blocks[root].proposer_index
    proposer_reputation = store.validator_reputations.get(proposer_index, BASE_REPUTATION_SCORE)
    proposer_bonus = (proposer_reputation / MAX_REPUTATION_SCORE) * 0.1 * attestation_weight

    # Calculate final score
    final_score = attestation_weight + quick_finality_boost + proposer_bonus

    # Cache the result
    store.block_scores[root] = final_score

    return final_score
def get_weber_head(store: Store) -> Root:
    """
    Return the head block root according to Weber's enhanced fork choice algorithm.
    """
    # Start from justified root
    head = store.best_justified_checkpoint.root

    # Climb up to current slot
    justified_slot = store.blocks[head].slot
    current_slot = (store.time - store.genesis_time) // SECONDS_PER_SLOT

    while justified_slot < current_slot:
        children = [
            root for root in store.blocks
            if store.blocks[root].parent_root == head
            and store.blocks[root].slot > justified_slot
        ]

        if len(children) == 0:
            # No children, return current head
            return head

        # Find heaviest child using Weber's block scoring
        head = max(children, key=lambda root: compute_block_score(store, root))
        justified_slot = store.blocks[head].slot

    return head

Weber Optimistic Fork Choice

Weber introduces an optimistic fork choice mechanism to accelerate consensus by quickly converging on a likely head even before full attestation collection.

Optimistic GHOST

def should_use_optimistic_head(store: Store) -> bool:
    """
    Determine if the optimistic head selection should be used.
    Depends on network health and validator participation.
    """
    # Check if we have enough validators online
    min_online_validators = len(store.validator_online_status) * 0.67
    online_count = sum(1 for status in store.validator_online_status.values() if status)
    if online_count < min_online_validators:
        return False

    # Check if chain is healthy
    if store.chain_health.participation_rate < 0.8:
        return False

    if store.chain_health.finality_distance > 4:
        return False

    return True
def get_optimistic_head(store: Store) -> Root:
    """
    Select a head based on optimistic metrics for faster convergence.
    """
    # Start with the standard justified root
    head = store.best_justified_checkpoint.root

    # Find the path with highest proposer reputation
    justified_slot = store.blocks[head].slot
    current_slot = (store.time - store.genesis_time) // SECONDS_PER_SLOT

    while justified_slot < current_slot:
        children = [
            root for root in store.blocks
            if store.blocks[root].parent_root == head
            and store.blocks[root].slot > justified_slot
        ]

        if len(children) == 0:
            return head

        # Score children by a combination of:
        # 1. Proposer reputation
        # 2. Initial attestations (with accelerated counting)
        # 3. Whether they extend the previously optimistic chain
        scored_children = []
        for child in children:
            proposer_index = store.blocks[child].proposer_index
            proposer_score = store.validator_reputations.get(proposer_index, BASE_REPUTATION_SCORE)

            # Initial attestation score with optimistic weighting
            attestation_score = compute_initial_attestation_score(store, child)

            # Boost score if this extends a previous optimistic selection
            previous_boost = 1.0
            for checkpoint in store.optimistic_checkpoints:
                if get_ancestor(store, child, store.blocks[checkpoint.root].slot) == checkpoint.root:
                    previous_boost = 1.0 + MAX_OPTIMISTIC_BOOST
                    break

            # Calculate final score for this child
            child_score = (proposer_score / MAX_REPUTATION_SCORE) * attestation_score * previous_boost
            scored_children.append((child, child_score))

        # Choose highest scoring child
        if not scored_children:
            return head

        head = max(scored_children, key=lambda item: item[1])[0]
        justified_slot = store.blocks[head].slot

    # Record this optimistic selection
    current_epoch = compute_epoch_at_slot(current_slot)
    store.optimistic_checkpoints.append(Checkpoint(epoch=current_epoch, root=head))

    # Keep only recent checkpoints
    if len(store.optimistic_checkpoints) > 10:
        store.optimistic_checkpoints.pop(0)

    return head

Fork choice with quick finality

Quick finality allows validators with high reputation to accelerate block finalization:

def on_quick_finality_vote(store: Store, vote: QuickFinalityVote) -> None:
    """
    Process a vote for quick finality from a highly reputable validator.
    """
    validator_index = vote.validator_index
    block_root = vote.block_root

    # Check if validator has high enough reputation
    if validator_index not in store.validator_reputations or store.validator_reputations[validator_index] < MIN_REPUTATION_FOR_QUICK_FINALITY:
        return

    # Register vote
    if block_root not in store.quick_finality_votes:
        store.quick_finality_votes[block_root] = set()

    store.quick_finality_votes[block_root].add(validator_index)

    # If threshold reached, update justified checkpoint if better than current
    vote_count = len(store.quick_finality_votes[block_root])
    if vote_count >= QUICK_FINALITY_THRESHOLD:
        # Get block and state
        block = store.blocks[block_root]
        state = store.block_states[block_root]

        # Create checkpoint
        quick_checkpoint = Checkpoint(
            epoch=compute_epoch_at_slot(block.slot),
            root=block_root
        )

        # If better than current justified, update best justified
        if quick_checkpoint.epoch > store.best_justified_checkpoint.epoch:
            store.best_justified_checkpoint = quick_checkpoint

Recovery from optimistic forks

Weber's optimistic fork choice includes safety mechanisms to recover from incorrect optimistic decisions:

def is_network_healthy(store: Store) -> bool:
    """
    Determine if the network is healthy enough for optimistic operations.
    """
    # Check participation rate
    if store.chain_health.participation_rate < 0.8:
        return False

    # Check finality lag
    current_epoch = compute_epoch_at_slot((store.time - store.genesis_time) // SECONDS_PER_SLOT)
    if current_epoch - store.chain_health.last_finalized_epoch > 4:
        return False

    # Check attestation inclusion time
    if store.chain_health.attestation_inclusion_time > 2 * SECONDS_PER_SLOT:
        return False

    return True
def is_checkpoint_justified_by_vote_count(store: Store, checkpoint: Checkpoint) -> bool:
    """
    Check if a checkpoint has enough votes to be considered justified by traditional means.
    """
    # Get state at checkpoint
    checkpoint_state = store.block_states[checkpoint.root]

    # Count votes for this checkpoint
    epoch = checkpoint.epoch
    total_balance = get_total_active_balance(checkpoint_state)
    voted_balance = Gwei(0)

    for validator_index, latest_message in store.validator_latest_messages.items():
        if latest_message.epoch >= epoch:
            attestation_root = latest_message.root
            if get_ancestor(store, attestation_root, store.blocks[checkpoint.root].slot) == checkpoint.root:
                if validator_index < len(checkpoint_state.validators):
                    voted_balance += checkpoint_state.validators[validator_index].effective_balance

    # Check if balance is sufficient for justification
    return voted_balance * 3 >= total_balance * 2  # 2/3 threshold
def check_and_revert_optimistic_decisions(store: Store) -> None:
    """
    Periodically check if optimistic decisions should be reverted.
    """
    # If the network seems unhealthy, fall back to standard fork choice
    if not is_network_healthy(store):
        # Clear optimistic votes
        store.quick_finality_votes = {}

        # Reset to standard justified checkpoint if it differs
        if store.best_justified_checkpoint != store.justified_checkpoint:
            proposed_justified = store.best_justified_checkpoint

            # Only keep it if it's properly justified by attestations
            if is_checkpoint_justified_by_vote_count(store, proposed_justified):
                # Keep the optimistic checkpoint
                pass
            else:
                # Revert to the standard justified checkpoint
                store.best_justified_checkpoint = store.justified_checkpoint

        # Recalculate head using standard algorithm
        get_weber_head(store)

Fork choice API

Actions

def on_tick(store: Store, time: uint64) -> None:
    """
    Called when the current time increases.
    """
    # Standard tick processing
    previous_time = store.time
    store.time = time

    # Check for any new attestation/block slots
    previous_slot = previous_time // SECONDS_PER_SLOT
    current_slot = time // SECONDS_PER_SLOT

    if current_slot > previous_slot:
        # Process any skipped slots
        for slot in range(previous_slot + 1, current_slot + 1):
            # Update state for skipped slots
            pass

    # Weber addition: Periodically check optimistic decisions
    if time % OPTIMISTIC_CHECK_INTERVAL == 0:
        check_and_revert_optimistic_decisions(store)

    # Update validator online status
    update_validator_online_status(store)
def on_attestation(store: Store, attestation: Attestation) -> None:
    """
    Called when an attestation is received.
    """
    # Validate attestation
    validate_attestation(store, attestation)

    # Standard attestation processing
    target = attestation.data.target
    beacon_block_root = attestation.data.beacon_block_root

    # Weber addition: Process with reputation weighting
    process_attestation_with_weight(store, attestation)

    # Update validator reputations
    for validator_index in get_attesting_indices(attestation):
        if validator_index in store.validator_reputations:
            # Update recent attestation time
            update_validator_recent_activity(store, validator_index)

    # Invalidate cached scores along this path
    invalidate_block_scores_for_attestation(store, attestation)
def on_block(store: Store, block: BeaconBlock) -> None:
    """
    Called when a block is received.
    """
    # Validate the block
    validate_block(store, block)

    # Get block root
    block_root = hash_tree_root(block)

    # Store the block
    store.blocks[block_root] = block

    # Store the post-state
    state = state_transition(store.block_states[block.parent_root], block)
    store.block_states[block_root] = state

    # Update justified checkpoint if needed
    if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
        # Update standard justified checkpoint
        store.justified_checkpoint = state.current_justified_checkpoint

    # Update finalized checkpoint if needed
    if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
        store.finalized_checkpoint = state.finalized_checkpoint

        # When finalizing a new epoch, we can cleanup old data
        cleanup_fork_choice_store(store)

    # Weber addition: Process quick finality votes in block
    for vote in block.body.quick_finality_votes:
        on_quick_finality_vote(store, vote)

    # Update block scores
    calculate_and_cache_block_score(store, block_root)

    # Update proposer reputation
    update_proposer_reputation(store, block.proposer_index)
1
2
3
4
5
6
7
8
9
def update_proposer_reputation(store: Store, proposer_index: ValidatorIndex) -> None:
    """
    Update the reputation of a block proposer.
    """
    # Reward proposers for timely blocks
    update_validator_reputation(store, proposer_index, REPUTATION_REWARD_BLOCK_PROPOSAL)

    # Note: In a full implementation, this would check the quality of the block
    # (e.g., including all available attestations, transactions, etc.)

Accessors

def get_head(store: Store) -> Root:
    """
    Get the current chain head according to Weber fork choice.
    This is the main entry point for fork choice.
    """
    # Check if we should use optimistic head selection
    if should_use_optimistic_head(store):
        return get_optimistic_head(store)
    else:
        return get_weber_head(store)
1
2
3
4
5
def get_justified_checkpoint(store: Store) -> Checkpoint:
    """
    Get the justified checkpoint.
    """
    return store.justified_checkpoint
1
2
3
4
5
def get_finalized_checkpoint(store: Store) -> Checkpoint:
    """
    Get the finalized checkpoint.
    """
    return store.finalized_checkpoint

Implementation strategies

Efficient fork choice updates

1
2
3
4
5
6
7
8
9
def invalidate_block_scores_for_attestation(store: Store, attestation: Attestation) -> None:
    """
    Efficiently invalidate cached scores that would be affected by this attestation.
    """
    # Get the attestation data root
    root = attestation.data.beacon_block_root

    # Invalidate this block and all descendants
    invalidate_block_and_descendants(store, root)
def invalidate_block_and_descendants(store: Store, root: Root) -> None:
    """
    Invalidate the score for a block and all its descendants.
    """
    if root in store.block_scores:
        del store.block_scores[root]

    # Recursively invalidate children
    for child in get_children(store, root):
        invalidate_block_and_descendants(store, child)
1
2
3
4
5
6
7
8
def get_children(store: Store, root: Root) -> List[Root]:
    """
    Get all child blocks of the block with the given root.
    """
    return [
        block_root for block_root in store.blocks
        if store.blocks[block_root].parent_root == root
    ]

Fork choice caching

1
2
3
4
5
6
7
def is_score_recent(store: Store, root: Root) -> bool:
    """
    Determine if a cached score is still valid.
    """
    # Check if we've received new attestations for this root since scoring
    # This is a simplified version - a real implementation would track attestation times
    return root in store.block_scores
1
2
3
4
5
6
7
def calculate_and_cache_block_score(store: Store, root: Root) -> float:
    """
    Calculate and cache the score for a block.
    """
    score = compute_block_score(store, root)
    store.block_scores[root] = score
    return score
1
2
3
4
5
6
7
def update_score_timestamp(store: Store, root: Root) -> None:
    """
    Record when a block's score was last calculated.
    """
    # In a real implementation, this would store the current time
    # For this specification, we'll assume it's handled implicitly
    pass

Attestation processing optimizations

def batch_process_attestations(store: Store, attestations: List[Attestation]) -> None:
    """
    Process multiple attestations efficiently in a batch.
    """
    # Group attestations by block root
    attestations_by_root = {}
    for attestation in attestations:
        root = attestation.data.beacon_block_root
        if root not in attestations_by_root:
            attestations_by_root[root] = []
        attestations_by_root[root].append(attestation)

    # Process each group efficiently
    for root, atts in attestations_by_root.items():
        batch_update_votes_for_root(store, root, atts)

    # Invalidate affected cached scores once per group
    for root in attestations_by_root:
        invalidate_block_and_descendants(store, root)
def batch_update_votes_for_root(store: Store, root: Root, attestations: List[Attestation]) -> None:
    """
    Update votes for a specific root in bulk.
    """
    # Count all validators voting for this root
    validators_for_root = set()

    for attestation in attestations:
        validators_for_root.update(get_attesting_indices(attestation))

    # Update latest messages in bulk
    target_epoch = attestations[0].data.target.epoch  # All attestations in batch have same target
    for validator_index in validators_for_root:
        store.validator_latest_messages[validator_index] = LatestMessage(
            epoch=target_epoch,
            root=root
        )

        # Update recent activity
        update_validator_recent_activity(store, validator_index)
def compute_initial_attestation_score(store: Store, root: Root) -> float:
    """
    Compute an initial attestation score for a block before full attestations are collected.
    This uses early attestations to predict the likely final weight.
    """
    # Get block and state
    block = store.blocks[root]
    state = store.block_states[root]

    # Check how many validators have already attested
    attesting_validators = set()
    for validator_index, latest_message in store.validator_latest_messages.items():
        if get_ancestor(store, latest_message.root, block.slot) == root:
            attesting_validators.add(validator_index)

    # Get total active balance
    active_indices = get_active_validator_indices(state, get_current_epoch(state))
    total_balance = sum(state.validators[i].effective_balance for i in active_indices)

    # Get attesting balance
    attesting_balance = sum(state.validators[i].effective_balance for i in attesting_validators)

    # Calculate time since block creation
    time_since_block = store.time - (store.genesis_time + block.slot * SECONDS_PER_SLOT)

    # Early attestations are weighted more heavily to project final score
    time_factor = max(1.0, SECONDS_PER_SLOT / max(1, time_since_block))
    projected_balance = min(total_balance, attesting_balance * time_factor)

    return projected_balance

Security considerations

Defense against attacks

The Weber fork choice incorporates several protections against common attack vectors:

  1. Reputation Weighted Votes: By weighting votes based on validator reputation, Weber mitigates the impact of misbehaving validators. Attackers would need to maintain high reputation scores while attacking, which creates contradicting incentives.

  2. Quick Finality Thresholds: The high reputation requirement for quick finality votes prevents new or misbehaving validators from influencing optimistic consensus.

  3. Fallback Mechanisms: The system automatically falls back to standard fork choice rules if network health metrics indicate potential attacks or issues.

  4. Economic Penalties: Through the reputation system, attackers face both direct penalties and reduced influence in the consensus process.

Incentive compatibility

The reputation-weighted fork choice creates aligned incentives for validators:

  1. Validators are incentivized to produce attestations promptly to maintain high reputation.
  2. Validators with high reputation gain more influence in the consensus process.
  3. The quick finality mechanism rewards validators for consistent, reliable performance.

This incentive structure rewards good behavior without significantly penalizing occasional honest mistakes or network issues.

Validator balancing

The Weber fork choice algorithm includes mechanisms to prevent centralization of influence:

  1. The reputation weighting is bounded to prevent any single validator from gaining excessive influence.
  2. Periodic reputation decay ensures that validators must continuously perform well.
  3. The validator rotation mechanism prevents long-term domination by specific validators.

Performance metrics

Weber's fork choice implementation maintains performance statistics to monitor health and efficiency:

  1. Convergence Time: How quickly nodes agree on a head block
  2. Reorg Frequency: How often the canonical chain changes
  3. Vote Participation: Percentage of active validators participating in consensus
  4. Optimistic Hit Rate: How often optimistic decisions match final consensus
  5. Score Calculation Efficiency: CPU time spent on score calculations

These metrics help implement