Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions src/lean_spec/subspecs/genesis/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import yaml
from pydantic import Field, field_validator, model_validator

from lean_spec.subspecs.containers import State, Validator
from lean_spec.subspecs.containers import Validator
from lean_spec.subspecs.containers.state import Validators
from lean_spec.subspecs.containers.validator import ValidatorIndex
from lean_spec.types import Bytes52, StrictBaseModel, Uint64
Expand Down Expand Up @@ -131,15 +131,6 @@ def to_validators(self) -> Validators:
]
)

def create_state(self) -> State:
"""
Generate the complete genesis state from this configuration.

Combines genesis time and validator set to create the initial
consensus state. This state becomes slot 0 for the chain.
"""
return State.generate_genesis(self.genesis_time, self.to_validators())

@classmethod
def from_yaml_file(cls, path: Path | str) -> GenesisConfig:
"""
Expand Down
62 changes: 0 additions & 62 deletions src/lean_spec/subspecs/koalabear/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,47 +19,6 @@
P_BYTES: Final = (P_BITS + 7) // 8
"""The size of a KoalaBear field element in bytes."""

TWO_ADICITY: Final = 24
"""
The largest integer n such that 2^n divides (P - 1).

P - 1 = 2^24 * 127
"""

TWO_ADIC_GENERATORS: Final[list[int]] = [
0x1,
0x7F000000,
0x7E010002,
0x6832FE4A,
0x8DBD69C,
0xA28F031,
0x5C4A5B99,
0x29B75A80,
0x17668B8A,
0x27AD539B,
0x334D48C7,
0x7744959C,
0x768FC6FA,
0x303964B2,
0x3E687D4D,
0x45A60E61,
0x6E2F4D7A,
0x163BD499,
0x6C4A8A45,
0x143EF899,
0x514DDCAD,
0x484EF19B,
0x205D63C3,
0x68E7DD49,
0x6AC49F88,
]
"""
A pre-computed list of 2^n-th roots of unity.

The element at index `n` is a generator for
the multiplicative subgroup of order 2^n.
"""


class Fp(SSZType):
"""
Expand Down Expand Up @@ -147,27 +106,6 @@ def __truediv__(self, other: Self) -> Self:
"""Field division."""
return self * other.inverse()

@classmethod
def two_adic_generator(cls, bits: int) -> Self:
"""
Get a generator for the multiplicative subgroup of order 2^bits.

This is a direct lookup from a pre-computed list of generators.

Args:
bits: The order of the subgroup will be 2^bits.
Must be in [0, TWO_ADICITY].

Returns:
A generator of the multiplicative subgroup of order 2^bits.

Raises:
ValueError: If `bits` is outside the valid range.
"""
if not (0 <= bits <= TWO_ADICITY):
raise ValueError(f"bits must be between 0 and {TWO_ADICITY}")
return cls(value=TWO_ADIC_GENERATORS[bits])

def __eq__(self, other: object) -> bool:
"""Check equality of two field elements."""
if not isinstance(other, Fp):
Expand Down
11 changes: 0 additions & 11 deletions src/lean_spec/subspecs/networking/enr/enr.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,6 @@ def get(self, key: EnrKey) -> bytes | None:
"""Get value by key, or None if absent."""
return self.pairs.get(key)

def has(self, key: EnrKey) -> bool:
"""Check if key is present."""
return key in self.pairs

@property
def identity_scheme(self) -> str | None:
"""Get identity scheme (should be "v4")."""
Expand Down Expand Up @@ -220,13 +216,6 @@ def is_valid(self) -> bool:
"""
return self.identity_scheme == self.SCHEME and self.public_key is not None

def is_compatible_with(self, other: ENR) -> bool:
"""Check fork compatibility via eth2 fork digest."""
self_eth2, other_eth2 = self.eth2_data, other.eth2_data
if self_eth2 is None or other_eth2 is None:
return False
return self_eth2.fork_digest == other_eth2.fork_digest

def _build_content_items(self) -> list[RLPItem]:
"""
Build the list of content items for RLP encoding.
Expand Down
9 changes: 0 additions & 9 deletions src/lean_spec/subspecs/networking/enr/eth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,6 @@ class Eth2Data(StrictBaseModel):
next_fork_epoch: Uint64
"""Epoch when next fork activates. FAR_FUTURE_EPOCH if none scheduled."""

@classmethod
def no_scheduled_fork(cls, current_digest: ForkDigest, current_version: Version) -> "Eth2Data":
"""Create Eth2Data indicating no scheduled fork."""
return cls(
fork_digest=current_digest,
next_fork_version=current_version,
next_fork_epoch=FAR_FUTURE_EPOCH,
)


class AttestationSubnets(BaseBitvector):
"""
Expand Down
12 changes: 1 addition & 11 deletions src/lean_spec/subspecs/networking/gossipsub/behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
import logging
import random
import time
from collections.abc import Callable, Coroutine
from collections.abc import Coroutine
from dataclasses import dataclass, field
from itertools import count
from typing import ClassVar, Final, cast
Expand Down Expand Up @@ -229,9 +229,6 @@ class GossipsubBehavior:
_heartbeat_task: asyncio.Task[None] | None = None
"""Background heartbeat task."""

_message_handler: Callable[[GossipsubMessageEvent], None] | None = None
"""Optional callback for received messages."""

_stop_event: asyncio.Event = field(default_factory=asyncio.Event)
"""Event to signal stop to the events generator."""

Expand Down Expand Up @@ -555,10 +552,6 @@ async def get_next_event(
pass
return None

def set_message_handler(self, handler: Callable[[GossipsubMessageEvent], None]) -> None:
"""Set a callback for received messages."""
self._message_handler = handler

async def _handle_rpc(self, peer_id: PeerId, rpc: RPC) -> None:
"""Dispatch an incoming RPC to the appropriate handlers."""
state = self._peers.get(peer_id)
Expand Down Expand Up @@ -651,9 +644,6 @@ async def _handle_message(self, peer_id: PeerId, msg: Message) -> None:
)
await self._event_queue.put(event)

if self._message_handler:
self._message_handler(event)

logger.debug(
"Received message %s from %s on topic %s", msg_id.hex()[:8], peer_id, msg.topic
)
Expand Down
18 changes: 0 additions & 18 deletions src/lean_spec/subspecs/networking/gossipsub/mcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,16 +226,6 @@ def shift(self) -> int:

return evicted

def clear(self) -> None:
"""Clear all cached messages."""
self._windows.clear()
self._windows.append(set())
self._by_id.clear()

def __len__(self) -> int:
"""Return the total number of cached messages."""
return len(self._by_id)


@dataclass(slots=True)
class SeenCache:
Expand Down Expand Up @@ -316,11 +306,3 @@ def cleanup(self, current_time: float) -> int:
del self._timestamps[msg_id]

return len(expired)

def clear(self) -> None:
"""Clear all seen entries."""
self._timestamps.clear()

def __len__(self) -> int:
"""Return the number of seen message IDs."""
return len(self._timestamps)
8 changes: 0 additions & 8 deletions src/lean_spec/subspecs/networking/gossipsub/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,14 +614,6 @@ def decode(cls, data: bytes) -> RPC:

return rpc

def is_empty(self) -> bool:
"""Check if this RPC contains no data."""
return (
not self.subscriptions
and not self.publish
and (self.control is None or self.control.is_empty())
)

@classmethod
def subscription(cls, topics: list[TopicId], subscribe: bool = True) -> RPC:
"""
Expand Down
44 changes: 1 addition & 43 deletions src/lean_spec/subspecs/networking/transport/identity/keypair.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec

from lean_spec.types import Bytes32, Bytes33
from lean_spec.types import Bytes33

from ..peer_id import KeyType, PeerId, PublicKeyProto

Expand All @@ -33,20 +33,6 @@ class Secp256k1PublicKey:
_key: ec.EllipticCurvePublicKey
"""The cryptography library's public key object"""

@classmethod
def from_bytes(cls, data: Bytes33) -> Secp256k1PublicKey:
"""
Load from 33-byte compressed SEC1 format.

Args:
data: 33-byte compressed secp256k1 public key.

Returns:
Parsed public key.
"""
key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), data)
return cls(_key=key)

def to_bytes(self) -> Bytes33:
"""
Return the 33-byte compressed SEC1 encoding.
Expand Down Expand Up @@ -107,34 +93,6 @@ def generate(cls) -> IdentityKeypair:
public_key = Secp256k1PublicKey(_key=private_key.public_key())
return cls(private_key=private_key, public_key=public_key)

@classmethod
def from_bytes(cls, data: Bytes32) -> IdentityKeypair:
"""
Load keypair from raw private key bytes.

Args:
data: 32-byte secp256k1 private key.

Returns:
Identity keypair.
"""
private_key = ec.derive_private_key(
int.from_bytes(data, "big"),
ec.SECP256K1(),
)
public_key = Secp256k1PublicKey(_key=private_key.public_key())
return cls(private_key=private_key, public_key=public_key)

def private_key_bytes(self) -> Bytes32:
"""
Return the raw 32-byte private key.

Returns:
32-byte private key scalar.
"""
private_numbers = self.private_key.private_numbers()
return Bytes32(private_numbers.private_value.to_bytes(32, "big"))

def sign(self, message: bytes) -> bytes:
"""
Sign a message with ECDSA-SHA256.
Expand Down
55 changes: 2 additions & 53 deletions src/lean_spec/subspecs/networking/transport/peer_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from typing import Final

from lean_spec.subspecs.networking import varint
from lean_spec.types import Bytes33

__all__ = [
# Main types
Expand Down Expand Up @@ -238,37 +237,6 @@ def encode(self) -> bytes:

return bytes([self.code, len(self.digest)]) + self.digest

@classmethod
def identity(cls, data: bytes) -> Multihash:
"""
Create an identity multihash (no hashing).

Args:
data: Data to wrap (must be <= 127 bytes).

Returns:
Identity multihash.

Raises:
ValueError: If data exceeds 127 bytes.
"""
if len(data) > 127:
raise ValueError("Identity multihash limited to 127 bytes")
return cls(code=MultihashCode.IDENTITY, digest=data)

@classmethod
def sha256(cls, data: bytes) -> Multihash:
"""
Create a SHA256 multihash.

Args:
data: Data to hash.

Returns:
SHA256 multihash.
"""
return cls(code=MultihashCode.SHA256, digest=hashlib.sha256(data).digest())

@classmethod
def from_data(cls, data: bytes) -> Multihash:
"""
Expand All @@ -286,9 +254,8 @@ def from_data(cls, data: bytes) -> Multihash:
Multihash with appropriate hash function.
"""
if len(data) <= _IDENTITY_THRESHOLD:
return cls.identity(data)
else:
return cls.sha256(data)
return cls(code=MultihashCode.IDENTITY, digest=data)
return cls(code=MultihashCode.SHA256, digest=hashlib.sha256(data).digest())


@dataclass(frozen=True, slots=True)
Expand Down Expand Up @@ -407,21 +374,3 @@ def from_public_key(cls, public_key: PublicKeyProto) -> PeerId:
encoded = public_key.encode()
mh = Multihash.from_data(encoded)
return cls(multihash=mh.encode())

@classmethod
def from_secp256k1(cls, public_key_bytes: Bytes33) -> PeerId:
"""
Derive PeerId from a secp256k1 compressed public key.

This is the standard method used by ream, zeam, and the Ethereum
libp2p network for peer identification.

Args:
public_key_bytes: 33-byte compressed secp256k1 public key
(starts with 0x02 or 0x03).

Returns:
Derived PeerId (starts with "16Uiu2..." for secp256k1).
"""
proto = PublicKeyProto(key_type=KeyType.SECP256K1, key_data=public_key_bytes)
return cls.from_public_key(proto)
21 changes: 0 additions & 21 deletions src/lean_spec/subspecs/sync/block_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,27 +295,6 @@ def unmark_orphan(self, root: Bytes32) -> None:
"""
self._orphans.discard(root)

def get_orphan_parents(self) -> list[Bytes32]:
"""
Get roots of missing parent blocks for all orphans.

This is the entry point for backfill. It returns a deduplicated list
of parent roots that need to be fetched to resolve current orphans.

Deduplication matters because multiple orphan blocks might share the
same missing parent (e.g., two competing blocks at the same slot).

Returns:
List of parent block roots to fetch via BlocksByRoot requests.
"""
return list(
{
pending.parent_root
for root in self._orphans
if (pending := self._blocks.get(root)) and pending.parent_root not in self._blocks
}
)

def get_children(self, parent_root: Bytes32) -> list[PendingBlock]:
"""
Get all cached children of a given parent root.
Expand Down
Loading
Loading