Skip to content

Commit 2ea3527

Browse files
authored
networking: add subspec scaffolding (leanEthereum#20)
* networking: add subspec scaffolding * use Checkpoint in Status
1 parent 0137a7e commit 2ea3527

6 files changed

Lines changed: 177 additions & 0 deletions

File tree

src/lean_spec/subspecs/chain/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
SLOT_DURATION_MS: Final = 4000
1616
"""The fixed duration of a single slot in milliseconds."""
1717

18+
SECONDS_PER_SLOT: Final = SLOT_DURATION_MS // 1000
19+
"""The fixed duration of a single slot in seconds."""
20+
21+
SLOTS_PER_EPOCH: Final = 96
22+
"""The number of slots in an epoch."""
23+
1824
PROPOSER_REORG_CUTOFF_BPS: Final = 2500
1925
"""
2026
The deadline within a slot (in basis points) for a proposer to publish a
@@ -74,6 +80,8 @@ class _ChainConfig(BaseModel):
7480

7581
# Time Parameters
7682
slot_duration_ms: Uint64
83+
second_per_slot: Uint64
84+
slots_per_epoch: Uint64
7785
proposer_reorg_cutoff_bps: BasisPoint
7886
vote_due_bps: BasisPoint
7987
fast_confirm_due_bps: BasisPoint
@@ -87,6 +95,8 @@ class _ChainConfig(BaseModel):
8795
# The Devnet Chain Configuration.
8896
DEVNET_CONFIG: Final = _ChainConfig(
8997
slot_duration_ms=SLOT_DURATION_MS,
98+
second_per_slot=SECONDS_PER_SLOT,
99+
slots_per_epoch=SLOTS_PER_EPOCH,
90100
proposer_reorg_cutoff_bps=PROPOSER_REORG_CUTOFF_BPS,
91101
vote_due_bps=VOTE_DUE_BPS,
92102
fast_confirm_due_bps=FAST_CONFIRM_DUE_BPS,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Exports the networking subspec components."""
2+
3+
from .config import (
4+
MAX_REQUEST_BLOCKS,
5+
MESSAGE_DOMAIN_INVALID_SNAPPY,
6+
MESSAGE_DOMAIN_VALID_SNAPPY,
7+
)
8+
from .gossipsub import GossipsubParameters
9+
from .messages import (
10+
BLOCKS_BY_ROOT_PROTOCOL_V1,
11+
STATUS_PROTOCOL_V1,
12+
BlocksByRootRequest,
13+
BlocksByRootResponse,
14+
Status,
15+
)
16+
from .types import DomainType, ProtocolId
17+
18+
__all__ = [
19+
"MAX_REQUEST_BLOCKS",
20+
"MESSAGE_DOMAIN_INVALID_SNAPPY",
21+
"MESSAGE_DOMAIN_VALID_SNAPPY",
22+
"GossipsubParameters",
23+
"BLOCKS_BY_ROOT_PROTOCOL_V1",
24+
"STATUS_PROTOCOL_V1",
25+
"BlocksByRootRequest",
26+
"BlocksByRootResponse",
27+
"Status",
28+
"DomainType",
29+
"ProtocolId",
30+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Networking Configuration Constants."""
2+
3+
from typing_extensions import Final
4+
5+
from .types import DomainType
6+
7+
MAX_REQUEST_BLOCKS: Final = 2**10
8+
"""Maximum number of blocks in a single request."""
9+
10+
MESSAGE_DOMAIN_INVALID_SNAPPY: Final = DomainType(b"\x00\x00\x00\x00")
11+
"""4-byte domain for gossip message-id isolation of invalid snappy messages."""
12+
13+
MESSAGE_DOMAIN_VALID_SNAPPY: Final = DomainType(b"\x01\x00\x00\x00")
14+
"""4-byte domain for gossip message-id isolation of valid snappy messages."""
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Gossipsub protocol parameters."""
2+
3+
from lean_spec.subspecs.chain.config import DEVNET_CONFIG
4+
from lean_spec.types import StrictBaseModel
5+
6+
7+
class GossipsubParameters(StrictBaseModel):
8+
"""A model holding the canonical gossipsub parameters."""
9+
10+
protocol_id: str = "/meshsub/1.0.0"
11+
"""The protocol ID for gossip messages."""
12+
13+
d: int = 8
14+
"""The target number of peers for a stable gossip mesh topic."""
15+
16+
d_low: int = 6
17+
"""
18+
The low watermark for the number of peers in a stable gossip mesh topic.
19+
"""
20+
21+
d_high: int = 12
22+
"""
23+
The high watermark for the number of peers in a stable gossip mesh topic.
24+
"""
25+
26+
d_lazy: int = 6
27+
"""The target number of peers for gossip-only connections."""
28+
29+
heartbeat_interval_secs: float = 0.7
30+
"""The frequency of the gossipsub heartbeat in seconds."""
31+
32+
fanout_ttl_secs: int = 60
33+
"""The time-to-live for fanout maps in seconds."""
34+
35+
mcache_len: int = 6
36+
"""The number of history windows to retain full messages in the cache."""
37+
38+
mcache_gossip: int = 3
39+
"""The number of history windows to gossip about."""
40+
41+
seen_ttl_secs: int = (
42+
DEVNET_CONFIG.second_per_slot * DEVNET_CONFIG.slots_per_epoch * 2
43+
)
44+
"""
45+
The expiry time in seconds for the cache of seen message IDs.
46+
47+
This is calculated as SECONDS_PER_SLOT * SLOTS_PER_EPOCH * 2.
48+
"""
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""
2+
Request/Response Domain Message Types.
3+
4+
This module defines the data structures for the peer-to-peer request/response
5+
domain. All messages are SSZ-encoded and then compressed with Snappy frames.
6+
"""
7+
8+
from pydantic import Field
9+
from typing_extensions import Annotated
10+
11+
from lean_spec.subspecs.containers import Checkpoint, SignedBlock
12+
from lean_spec.types import Bytes32, StrictBaseModel
13+
14+
from .config import MAX_REQUEST_BLOCKS
15+
from .types import ProtocolId
16+
17+
# --- Status v1 ---
18+
19+
STATUS_PROTOCOL_V1: ProtocolId = "/leanconsensus/req/status/1/"
20+
"""The protocol ID for the Status v1 request/response message."""
21+
22+
23+
class Status(StrictBaseModel):
24+
"""
25+
The Status message, used by clients to share their chain state.
26+
27+
This is the first message sent upon a new connection and is essential for
28+
the peer-to-peer handshake. It allows nodes to verify compatibility and
29+
determine if they are on the same chain.
30+
"""
31+
32+
finalized: Checkpoint
33+
"""The client's latest finalized checkpoint."""
34+
35+
head: Checkpoint
36+
"""The client's current head checkpoint."""
37+
38+
39+
# --- BlocksByRoot v1 ---
40+
41+
BLOCKS_BY_ROOT_PROTOCOL_V1: ProtocolId = "/leanconsensus/req/blocks_by_root/1/"
42+
"""The protocol ID for the BlocksByRoot v1 request/response message."""
43+
44+
BlocksByRootRequest = Annotated[
45+
list[Bytes32],
46+
Field(max_length=MAX_REQUEST_BLOCKS),
47+
]
48+
"""
49+
A request for one or more blocks by their root hashes.
50+
51+
This is primarily used to recover recent or missing blocks from a peer.
52+
"""
53+
54+
BlocksByRootResponse = Annotated[
55+
list[SignedBlock],
56+
Field(max_length=MAX_REQUEST_BLOCKS),
57+
]
58+
"""
59+
A response containing the requested `SignedBlock` objects.
60+
61+
The length of the list may be less than the number of requested blocks if
62+
the responding peer does not have all of them. Each block is sent in a
63+
separate `response_chunk`.
64+
"""
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""Networking-related type definitions for the specification."""
2+
3+
from typing import Annotated
4+
5+
from pydantic import Field
6+
7+
DomainType = Annotated[bytes, Field(min_length=4, max_length=4)]
8+
"""A 4-byte value used for domain separation in message-ids."""
9+
10+
ProtocolId = str
11+
"""A string representing a libp2p protocol ID."""

0 commit comments

Comments
 (0)