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:
- Responding to bootstrap requests with accurate sync committee data
- Providing updates on finalized and optimistic chain heads
- Serving compressed Merkle proofs for state validation
- 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
| 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
| 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
| 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:
- Peer reputation score
- Request type (finality updates prioritized over historical data)
- 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:
- Rate limiting requests per peer
- Limiting maximum response sizes
- Monitoring system resource usage
- Maintaining connection limits
- Implementing circuit breakers for abnormal behavior patterns
Eclipse Attack Mitigation
Full nodes should implement safeguards against eclipse attacks on light clients:
- Respond with verifiable data that allows light clients to detect inconsistencies
- Include timestamps in update responses
- Sign responses to prevent tampering
- Include chain difficulty information to help detect minority chains
Implementation Recommendations
Caching Strategies
Full nodes should implement efficient caching strategies:
- Cache frequently requested light client updates
- Pre-compute sync committee period boundary updates
- Apply time-based cache invalidation for optimistic updates
- Utilize LRU (Least Recently Used) cache eviction policy
Recommended optimizations for light client serving:
- Parallel processing of multiple light client requests
- Asynchronous proof generation
- Batched signature verification
- Progressive response streaming for large data requests
- Compressed response formats