Skip to content

Weber Light Client -- Full Node

Notice: This document is a work-in-progress for researchers and implementers.

Table of contents

Introduction

This specification defines the responsibilities and behavior of full nodes in the Weber protocol when serving light clients. Weber extends previous light client implementations with optimizations for resource-constrained environments and improved security features.

Constants

Name Value Description
MAX_LIGHT_CLIENT_REQUESTS_PER_MINUTE 60 Maximum allowed light client requests per minute from a single peer
MAX_CONCURRENT_LIGHT_CLIENT_SESSIONS 1024 Maximum number of concurrent light client sessions to serve
LIGHT_CLIENT_DATA_CACHE_SIZE 128 Number of recent light client updates to cache
WEBER_OPTIMIZED_PROOF_ENABLED True Whether to use Weber's optimized Merkle proof format
LIGHT_CLIENT_SESSION_TIMEOUT 1800 Session timeout in seconds (30 minutes)

Full Node Responsibilities

Light Client Data Serving

Full nodes in the Weber network are expected to provide light client services including:

  1. Responding to bootstrap requests with accurate sync committee data
  2. Providing updates on finalized and optimistic chain heads
  3. Serving compressed Merkle proofs for state validation
  4. Supporting resource-aware sync modes for bandwidth-constrained clients

Light Client Request Handling

def handle_light_client_request(request, peer_id):
    """Process an incoming light client request."""
    # Validate request and rate limit
    if not validate_request_format(request):
        return error_response("INVALID_REQUEST_FORMAT")

    if is_rate_limited(peer_id):
        return error_response("RATE_LIMITED")

    # Process by request type
    if request.type == LC_REQUEST_BOOTSTRAP:
        return create_bootstrap_response(request.block_root)
    elif request.type == LC_REQUEST_UPDATES:
        return create_updates_response(
            request.start_period, 
            request.count,
            request.compression_level
        )
    elif request.type == LC_REQUEST_FINALITY_UPDATE:
        return create_finality_update_response()
    elif request.type == LC_REQUEST_OPTIMISTIC_UPDATE:
        return create_optimistic_update_response()
    else:
        return error_response("UNKNOWN_REQUEST_TYPE")

Reputation Tracking

Full nodes maintain reputation scores for light clients to prevent abuse:

def update_light_client_reputation(peer_id, request_type, behavior_score):
    """Update reputation score for a light client peer."""
    if peer_id not in reputation_registry:
        reputation_registry[peer_id] = DEFAULT_REPUTATION_SCORE

    # Adjust reputation based on behavior
    reputation_registry[peer_id] += behavior_score

    # Apply bounds
    reputation_registry[peer_id] = max(
        MIN_REPUTATION_SCORE,
        min(MAX_REPUTATION_SCORE, reputation_registry[peer_id])
    )

    # Apply reputation-based service levels
    if reputation_registry[peer_id] < POOR_REPUTATION_THRESHOLD:
        apply_service_restrictions(peer_id)

Data Structures

Reputation Registry

1
2
3
4
5
6
7
class ReputationEntry(Container):
    score: uint32
    first_seen_timestamp: uint64
    last_request_timestamp: uint64
    request_count: uint64
    invalid_request_count: uint16
    bytes_served: uint64

Light Client Session

1
2
3
4
5
6
7
8
class LightClientSession(Container):
    peer_id: PeerId
    session_id: uint64
    start_timestamp: uint64
    last_request_timestamp: uint64
    request_count: uint32
    total_data_served: uint64
    compression_preference: uint8

Helper functions

Modified block_to_light_client_header

def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader:
    epoch = compute_epoch_at_slot(block.message.slot)

    if epoch >= WEBER_FORK_EPOCH:
        payload = block.message.body.execution_payload
        execution_header = WeberExecutionPayloadHeader(
            # Standard execution header fields
            parent_hash=payload.parent_hash,
            fee_recipient=payload.fee_recipient,
            state_root=payload.state_root,
            receipts_root=payload.receipts_root,
            logs_bloom=payload.logs_bloom,
            prev_randao=payload.prev_randao,
            block_number=payload.block_number,
            gas_limit=payload.gas_limit,
            gas_used=payload.gas_used,
            timestamp=payload.timestamp,
            extra_data=payload.extra_data,
            base_fee_per_gas=payload.base_fee_per_gas,
            block_hash=payload.block_hash,
            transactions_root=hash_tree_root(payload.transactions),
            withdrawals_root=hash_tree_root(payload.withdrawals),
            # Weber additions
            reputation_root=hash_tree_root(payload.validator_reputation_update),
            dynamic_fee_parameters=payload.dynamic_fee_parameters
        )
        execution_branch = WeberExecutionBranch(
            compute_optimized_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX)
        )
    elif epoch >= CAPELLA_FORK_EPOCH:
        payload = block.message.body.execution_payload
        execution_header = ExecutionPayloadHeader(
            parent_hash=payload.parent_hash,
            fee_recipient=payload.fee_recipient,
            state_root=payload.state_root,
            receipts_root=payload.receipts_root,
            logs_bloom=payload.logs_bloom,
            prev_randao=payload.prev_randao,
            block_number=payload.block_number,
            gas_limit=payload.gas_limit,
            gas_used=payload.gas_used,
            timestamp=payload.timestamp,
            extra_data=payload.extra_data,
            base_fee_per_gas=payload.base_fee_per_gas,
            block_hash=payload.block_hash,
            transactions_root=hash_tree_root(payload.transactions),
            withdrawals_root=hash_tree_root(payload.withdrawals),
        )
        execution_branch = ExecutionBranch(
            compute_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX)
        )
    else:
        # Earlier forks
        execution_header = ExecutionPayloadHeader()
        execution_branch = ExecutionBranch()

    return WeberLightClientHeader(
        beacon=BeaconBlockHeader(
            slot=block.message.slot,
            proposer_index=block.message.proposer_index,
            parent_root=block.message.parent_root,
            state_root=block.message.state_root,
            body_root=hash_tree_root(block.message.body),
        ),
        execution=execution_header,
        execution_branch=execution_branch,
        # Weber additions
        validator_reputation=block.message.body.validator_reputation_summary,
        quick_finality_signature=block.quick_finality_signature if epoch >= WEBER_FORK_EPOCH else None
    )

Weber Enhanced compute_merkle_proof

def compute_optimized_merkle_proof(container, gindex):
    """
    Compute a Weber-optimized Merkle proof for the given container and generalized index.
    """
    # Regular Merkle proof computation
    proof = compute_merkle_proof(container, gindex)

    # Apply Weber-specific proof compression
    compressed_proof = compress_merkle_proof(proof)

    return compressed_proof

Optimized create_light_client_update

def create_light_client_update(
    current_slot,
    attested_block,
    finalized_block,
    sync_aggregate,
    next_sync_committee=None
):
    """
    Create a light client update with Weber optimizations.
    """
    # Create the standard update fields
    update = LightClientUpdate(
        attested_header=block_to_light_client_header(attested_block),
        finalized_header=block_to_light_client_header(finalized_block),
        finality_branch=compute_optimized_merkle_proof(
            attested_block.message.state_root, 
            FINALIZED_ROOT_INDEX
        ),
        sync_aggregate=sync_aggregate,
        signature_slot=current_slot
    )

    # Add next sync committee if at period boundary
    if next_sync_committee is not None:
        update.next_sync_committee = next_sync_committee
        update.next_sync_committee_branch = compute_optimized_merkle_proof(
            attested_block.message.state_root, 
            NEXT_SYNC_COMMITTEE_INDEX
        )

    # Weber enhancement: Add compression indicator
    update.is_compressed = True

    return update

Network Behavior

Request Prioritization

Full nodes should prioritize light client requests based on:

  1. Peer reputation score
  2. Request type (finality updates prioritized over historical data)
  3. Current network load and resource availability

This ensures efficient use of resources while providing responsive service to well-behaved clients.

Bandwidth Management

def apply_bandwidth_management(peer_id, request_size):
    """
    Apply bandwidth management policies for light client requests.
    """
    # Check if node is under high load
    if system_under_high_load():
        # Apply more restrictive limits
        max_request_size = LOW_BANDWIDTH_MAX_REQUEST_SIZE
        max_requests_per_minute = LOW_BANDWIDTH_REQUEST_RATE
    else:
        # Normal operation limits
        max_request_size = STANDARD_MAX_REQUEST_SIZE
        max_requests_per_minute = STANDARD_REQUEST_RATE

    # Apply reputation-based adjustments
    reputation = get_peer_reputation(peer_id)
    if reputation > HIGH_REPUTATION_THRESHOLD:
        # Increase limits for high-reputation peers
        max_request_size *= 1.5
        max_requests_per_minute *= 1.5

    # Check against limits
    if request_size > max_request_size:
        return False, "REQUEST_TOO_LARGE"

    if request_rate_exceeded(peer_id, max_requests_per_minute):
        return False, "RATE_LIMIT_EXCEEDED"

    return True, ""

Security Considerations

Resource Exhaustion Prevention

Full nodes must implement measures to prevent resource exhaustion attacks:

  1. Rate limiting requests per peer
  2. Limiting maximum response sizes
  3. Monitoring system resource usage
  4. Maintaining connection limits
  5. Implementing circuit breakers for abnormal behavior patterns

Eclipse Attack Mitigation

Full nodes should implement safeguards against eclipse attacks on light clients:

  1. Respond with verifiable data that allows light clients to detect inconsistencies
  2. Include timestamps in update responses
  3. Sign responses to prevent tampering
  4. Include chain difficulty information to help detect minority chains

Implementation Recommendations

Caching Strategies

Full nodes should implement efficient caching strategies:

  1. Cache frequently requested light client updates
  2. Pre-compute sync committee period boundary updates
  3. Apply time-based cache invalidation for optimistic updates
  4. Utilize LRU (Least Recently Used) cache eviction policy

Performance Optimizations

Recommended optimizations for light client serving:

  1. Parallel processing of multiple light client requests
  2. Asynchronous proof generation
  3. Batched signature verification
  4. Progressive response streaming for large data requests
  5. Compressed response formats