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
3 changes: 2 additions & 1 deletion src/lean_spec/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import sys
import time
from pathlib import Path
from typing import Final

from lean_spec.subspecs.chain.config import ATTESTATION_COMMITTEE_COUNT
from lean_spec.subspecs.containers import Block, BlockBody, Checkpoint, State
Expand All @@ -55,7 +56,7 @@
#
# Must match the fork string used by ream and other clients.
# For devnet, this is "devnet0".
GOSSIP_FORK_DIGEST = "devnet0"
GOSSIP_FORK_DIGEST: Final = "devnet0"

logger = logging.getLogger(__name__)

Expand Down
5 changes: 4 additions & 1 deletion src/lean_spec/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
This module contains environment-specific settings that apply across all subspecs.
"""

from __future__ import annotations

import os
from typing import Final

_SUPPORTED_LEAN_ENVS: list[str] = ["prod", "test"]

LEAN_ENV = os.environ.get("LEAN_ENV", "prod").lower()
LEAN_ENV: Final = os.environ.get("LEAN_ENV", "prod").lower()
"""The environment flag ('prod' or 'test'). Defaults to 'prod' for the specs."""

if LEAN_ENV not in _SUPPORTED_LEAN_ENVS:
Expand Down
10 changes: 0 additions & 10 deletions src/lean_spec/snappy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@
It prioritizes speed over compression ratio, making it ideal for real-time
applications and network protocols.

Usage::

from lean_spec.snappy import compress, decompress

# Compress data before sending
compressed = compress(data)

# Decompress received data
original = decompress(compressed)

The implementation follows the Snappy format specification:
https://github.com/google/snappy/blob/main/format_description.txt
"""
Expand Down
46 changes: 24 additions & 22 deletions src/lean_spec/snappy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@

from __future__ import annotations

from typing import Final

#
# Snappy processes data in fixed-size blocks to bound memory usage and
# enable streaming. Each block is compressed independently.

BLOCK_LOG: int = 16
BLOCK_LOG: Final = 16
"""Log2 of the maximum block size (2^16 = 65536 bytes)."""

BLOCK_SIZE: int = 1 << BLOCK_LOG
BLOCK_SIZE: Final = 1 << BLOCK_LOG
"""Maximum block size in bytes (64 KB).

Large inputs are split into 64 KB blocks, each compressed independently.
This bounds memory usage and enables streaming decompression.
"""

INPUT_MARGIN_BYTES: int = 15
INPUT_MARGIN_BYTES: Final = 15
"""Safety margin at end of input for batch reads.

The compressor reads up to 8 bytes at a time for efficiency. This margin
Expand All @@ -36,15 +38,15 @@
# 10 = Copy with 2-byte offset (max 65535 bytes back)
# 11 = Copy with 4-byte offset (max 4GB back)

LITERAL: int = 0b00
LITERAL: Final = 0b00
"""Tag type for literal (uncompressed) data.

Literals are sequences of bytes copied verbatim from input to output.
Short literals (1-60 bytes) encode the length in the tag byte itself.
Longer literals use 1-4 additional bytes for the length.
"""

COPY_1_BYTE_OFFSET: int = 0b01
COPY_1_BYTE_OFFSET: Final = 0b01
"""Tag type for copy with 1-byte offset.

Compact encoding for short backreferences:
Expand All @@ -54,7 +56,7 @@
Total encoding: 2 bytes (tag + offset).
"""

COPY_2_BYTE_OFFSET: int = 0b10
COPY_2_BYTE_OFFSET: Final = 0b10
"""Tag type for copy with 2-byte offset.

Standard encoding for medium backreferences:
Expand All @@ -64,7 +66,7 @@
Total encoding: 3 bytes (tag + 2 offset bytes).
"""

COPY_4_BYTE_OFFSET: int = 0b11
COPY_4_BYTE_OFFSET: Final = 0b11
"""Tag type for copy with 4-byte offset.

Extended encoding for long backreferences:
Expand All @@ -79,13 +81,13 @@
# The compressor uses a hash table to find matching sequences in previously
# seen data. The hash table maps 4-byte sequences to their positions.

MIN_HASH_TABLE_BITS: int = 8
MIN_HASH_TABLE_BITS: Final = 8
"""Minimum hash table size exponent (2^8 = 256 entries)."""

MAX_HASH_TABLE_BITS: int = 15
MAX_HASH_TABLE_BITS: Final = 15
"""Maximum hash table size exponent (2^15 = 32768 entries)."""

HASH_MULTIPLIER: int = 0x1E35A7BD
HASH_MULTIPLIER: Final = 0x1E35A7BD
"""Magic constant for the hash function.

This is a prime-like constant that spreads input bits well across
Expand All @@ -98,67 +100,67 @@
# 1-60 bytes: Length stored in upper 6 bits of tag byte.
# 61+ bytes: Tag byte indicates extra length bytes follow.

MAX_INLINE_LITERAL_LENGTH: int = 60
MAX_INLINE_LITERAL_LENGTH: Final = 60
"""Maximum literal length that fits in the tag byte.

For lengths 1-60, we encode (length - 1) in the upper 6 bits of the tag.
For lengths > 60, we use additional bytes to encode the length.
"""

LITERAL_LENGTH_1_BYTE: int = 60
LITERAL_LENGTH_1_BYTE: Final = 60
"""Tag marker indicating 1 additional byte for literal length.

The actual length is stored as a single byte following the tag.
Supports literals up to 256 bytes.
"""

LITERAL_LENGTH_2_BYTES: int = 61
LITERAL_LENGTH_2_BYTES: Final = 61
"""Tag marker indicating 2 additional bytes for literal length.

The actual length is stored as little-endian uint16 following the tag.
Supports literals up to 65536 bytes.
"""

LITERAL_LENGTH_3_BYTES: int = 62
LITERAL_LENGTH_3_BYTES: Final = 62
"""Tag marker indicating 3 additional bytes for literal length.

The actual length is stored as little-endian uint24 following the tag.
Supports literals up to 16777216 bytes.
"""

LITERAL_LENGTH_4_BYTES: int = 63
LITERAL_LENGTH_4_BYTES: Final = 63
"""Tag marker indicating 4 additional bytes for literal length.

The actual length is stored as little-endian uint32 following the tag.
Supports literals up to 4294967296 bytes.
"""

MAX_COPY_1_LENGTH: int = 11
MAX_COPY_1_LENGTH: Final = 11
"""Maximum copy length for 1-byte offset encoding (4-11 bytes)."""

MIN_COPY_1_LENGTH: int = 4
MIN_COPY_1_LENGTH: Final = 4
"""Minimum copy length for 1-byte offset encoding."""

MAX_COPY_1_OFFSET: int = 2047
MAX_COPY_1_OFFSET: Final = 2047
"""Maximum offset for 1-byte offset encoding (11 bits)."""

MAX_COPY_2_OFFSET: int = 65535
MAX_COPY_2_OFFSET: Final = 65535
"""Maximum offset for 2-byte offset encoding (16 bits)."""

#
# The uncompressed length is encoded as a varint at the start of the
# compressed data. Varints use 7 bits per byte, with the high bit
# indicating continuation.

MAX_VARINT_LENGTH: int = 5
MAX_VARINT_LENGTH: Final = 5
"""Maximum bytes needed for a 32-bit varint.

Each byte encodes 7 bits, so 5 bytes can encode up to 35 bits.
This is sufficient for any 32-bit value.
"""

VARINT_CONTINUATION_BIT: int = 0x80
VARINT_CONTINUATION_BIT: Final = 0x80
"""High bit set in varint bytes to indicate more bytes follow."""

VARINT_DATA_MASK: int = 0x7F
VARINT_DATA_MASK: Final = 0x7F
"""Mask to extract the 7 data bits from a varint byte."""
14 changes: 8 additions & 6 deletions src/lean_spec/snappy/framing.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,13 @@

from __future__ import annotations

from typing import Final

from .compress import compress as raw_compress
from .decompress import SnappyDecompressionError
from .decompress import decompress as raw_decompress

STREAM_IDENTIFIER: bytes = b"\xff\x06\x00\x00sNaPpY"
STREAM_IDENTIFIER: Final = b"\xff\x06\x00\x00sNaPpY"
"""Stream identifier marking the start of a Snappy framed stream.

Format: [type=0xff][length=6 as 3-byte LE][magic="sNaPpY"]
Expand All @@ -100,28 +102,28 @@
It may also appear later (e.g., when streams are concatenated).
"""

CHUNK_TYPE_COMPRESSED: int = 0x00
CHUNK_TYPE_COMPRESSED: Final = 0x00
"""Chunk type for Snappy-compressed data.

Chunk data format: [masked_crc32c: 4 bytes LE][compressed_payload]
The CRC covers the UNCOMPRESSED data, not the compressed payload.
"""

CHUNK_TYPE_UNCOMPRESSED: int = 0x01
CHUNK_TYPE_UNCOMPRESSED: Final = 0x01
"""Chunk type for uncompressed (raw) data.

Chunk data format: [masked_crc32c: 4 bytes LE][raw_payload]
Used when compression would expand the data (e.g., random bytes).
"""

MAX_UNCOMPRESSED_CHUNK_SIZE: int = 65536
MAX_UNCOMPRESSED_CHUNK_SIZE: Final = 65536
"""Maximum uncompressed data per chunk (64 KiB).

This limit enables fixed-size decompression buffers.
Chunks exceeding this limit are rejected.
"""

CRC32C_MASK_DELTA: int = 0xA282EAD8
CRC32C_MASK_DELTA: Final = 0xA282EAD8
"""Constant added during CRC masking.

From the spec: "Rotate right by 15 bits, then add 0xa282ead8."
Expand Down Expand Up @@ -167,7 +169,7 @@ def _crc32c_table() -> list[int]:


# Pre-compute the table at module load time.
_CRC32C_TABLE: list[int] = _crc32c_table()
_CRC32C_TABLE: tuple[int, ...] = tuple(_crc32c_table())


def _crc32c(data: bytes) -> int:
Expand Down
5 changes: 3 additions & 2 deletions src/lean_spec/subspecs/api/endpoints/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from __future__ import annotations

import json
from typing import Final

from aiohttp import web

STATUS_HEALTHY = "healthy"
STATUS_HEALTHY: Final = "healthy"
"""Fixed healthy status returned by the health endpoint."""

SERVICE_NAME = "lean-rpc-api"
SERVICE_NAME: Final = "lean-rpc-api"
"""Fixed service identifier returned by the health endpoint."""


Expand Down
2 changes: 2 additions & 0 deletions src/lean_spec/subspecs/api/routes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""API route definitions."""

from __future__ import annotations

from collections.abc import Awaitable, Callable

from aiohttp import web
Expand Down
2 changes: 2 additions & 0 deletions src/lean_spec/subspecs/chain/clock.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
coordinate block proposals and attestations.
"""

from __future__ import annotations

import asyncio
from collections.abc import Callable
from dataclasses import dataclass
Expand Down
4 changes: 2 additions & 2 deletions src/lean_spec/subspecs/chain/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from lean_spec.types.uint import Uint64

INTERVALS_PER_SLOT = Uint64(5)
INTERVALS_PER_SLOT: Final = Uint64(5)
"""Number of intervals per slot for forkchoice processing."""

SECONDS_PER_SLOT: Final = Uint64(4)
Expand All @@ -13,7 +13,7 @@
MILLISECONDS_PER_SLOT: Final = SECONDS_PER_SLOT * Uint64(1000)
"""The fixed duration of a single slot in milliseconds."""

MILLISECONDS_PER_INTERVAL = MILLISECONDS_PER_SLOT // INTERVALS_PER_SLOT
MILLISECONDS_PER_INTERVAL: Final = MILLISECONDS_PER_SLOT // INTERVALS_PER_SLOT
"""Milliseconds per forkchoice processing interval."""

JUSTIFICATION_LOOKBACK_SLOTS: Final = Uint64(3)
Expand Down
3 changes: 2 additions & 1 deletion src/lean_spec/subspecs/containers/slot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from __future__ import annotations

import math
from typing import Final

from lean_spec.types import Uint64

IMMEDIATE_JUSTIFICATION_WINDOW = 5
IMMEDIATE_JUSTIFICATION_WINDOW: Final = 5
"""First N slots after finalization are always justifiable."""


Expand Down
4 changes: 3 additions & 1 deletion src/lean_spec/subspecs/containers/state/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from __future__ import annotations

from typing import TYPE_CHECKING, AbstractSet, Collection, Iterable
from collections.abc import Collection, Iterable
from collections.abc import Set as AbstractSet
from typing import TYPE_CHECKING

from lean_spec.subspecs.chain.clock import Interval
from lean_spec.subspecs.chain.config import INTERVALS_PER_SLOT
Expand Down
14 changes: 8 additions & 6 deletions src/lean_spec/subspecs/koalabear/field.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
"""Core definition of the KoalaBear prime field Fp."""

from typing import IO, Self
from __future__ import annotations

from typing import IO, Final, Self

from lean_spec.types import SSZType

P: int = 2**31 - 2**24 + 1
P: Final = 2**31 - 2**24 + 1
"""
The KoalaBear Prime: P = 2^31 - 2^24 + 1

The prime is chosen because the cube map (x -> x^3) is an automorphism of the multiplicative group.
"""

P_BITS: int = 31
P_BITS: Final = 31
"""The number of bits in the prime P."""

P_BYTES: int = (P_BITS + 7) // 8
P_BYTES: Final = (P_BITS + 7) // 8
"""The size of a KoalaBear field element in bytes."""

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

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

TWO_ADIC_GENERATORS: list[int] = [
TWO_ADIC_GENERATORS: Final[list[int]] = [
0x1,
0x7F000000,
0x7E010002,
Expand Down
4 changes: 2 additions & 2 deletions src/lean_spec/subspecs/networking/client/event_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
import asyncio
import logging
from dataclasses import dataclass, field
from typing import Protocol, Self
from typing import Final, Protocol, Self

from lean_spec.snappy import SnappyDecompressionError, frame_decompress
from lean_spec.subspecs.containers import SignedBlockWithAttestation
Expand Down Expand Up @@ -187,7 +187,7 @@ class GossipMessageError(Exception):
"""Raised when a gossip message cannot be processed."""


SUPPORTED_PROTOCOLS: frozenset[str] = (
SUPPORTED_PROTOCOLS: Final[frozenset[str]] = (
frozenset({GOSSIPSUB_DEFAULT_PROTOCOL_ID, GOSSIPSUB_PROTOCOL_ID_V12}) | REQRESP_PROTOCOL_IDS
)
"""Protocols supported for incoming stream negotiation.
Expand Down
Loading
Loading