Skip to content

Commit 45a64c8

Browse files
authored
Merge branch 'libp2p:main' into main
2 parents 727901f + 22ea173 commit 45a64c8

49 files changed

Lines changed: 1159 additions & 150 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/bitswap/bitswap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
# Silence verbose loggers
3131
logging.getLogger("multiaddr.transforms").setLevel(logging.WARNING)
3232
logging.getLogger("multiaddr.codecs.cid").setLevel(logging.WARNING)
33-
logging.getLogger("async_service.Manager").setLevel(logging.WARNING)
33+
logging.getLogger("libp2p.tools.async_service.base").setLevel(logging.WARNING)
3434

3535
logger = logging.getLogger(__name__)
3636

examples/doc-examples/example_mplex_timeouts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
# Suppress debug logging from libp2p components
4848
logging.getLogger("multiaddr").setLevel(logging.ERROR)
4949
logging.getLogger("libp2p").setLevel(logging.ERROR)
50-
logging.getLogger("async_service").setLevel(logging.ERROR)
50+
logging.getLogger("libp2p.tools.async_service.base").setLevel(logging.ERROR)
5151

5252
PROTOCOL_ID = TProtocol("/timeout-demo/1.0.0")
5353
TEST_MESSAGE = b"Hello, timeout world!"
@@ -380,7 +380,7 @@ def create_parser() -> argparse.ArgumentParser:
380380
# Re-enable debug logging for libp2p components when verbose
381381
logging.getLogger("multiaddr").setLevel(logging.DEBUG)
382382
logging.getLogger("libp2p").setLevel(logging.DEBUG)
383-
logging.getLogger("async_service").setLevel(logging.DEBUG)
383+
logging.getLogger("libp2p.tools.async_service.base").setLevel(logging.DEBUG)
384384
print("🔍 Verbose logging enabled")
385385

386386
try:

interop/transport/NEW_SPEC_2026.md

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Python Implementation for Transport Test Framework
2+
3+
## Summary
4+
5+
This document describes the Python libp2p v0.x implementation for the transport test framework. The implementation follows the transport test framework specification with proper Redis coordination protocol, correct environment variable handling, standalone transport support, and YAML-formatted output.
6+
7+
## Implementation Details
8+
9+
### 1. Redis Coordination Protocol
10+
11+
**Implementation**: Uses `RPUSH`/`BLPOP` (Redis list operations) as specified in the transport test framework, matching Rust and JavaScript implementations.
12+
13+
**Listener** (`run_listener` method):
14+
15+
- Uses `DEL` operation before `RPUSH` to prevent `WRONGTYPE` errors from stale data
16+
- Uses `RPUSH` to publish listener multiaddr as a list element
17+
- Key format: `{TEST_KEY}_listener_multiaddr`
18+
19+
**Dialer** (`run_dialer` method):
20+
21+
- Uses `BLPOP` (blocking list pop) to wait for listener address
22+
- Properly handles BLPOP return value `(key, value)` tuple format
23+
- Includes timeout handling with proper conversion for Redis commands
24+
25+
**Current Implementation**:
26+
27+
```python
28+
# Listener: Publish address
29+
redis_key = f"{self.test_key}_listener_multiaddr"
30+
31+
# Clean up any existing key to ensure it's a list type
32+
try:
33+
self.redis_client.delete(redis_key)
34+
except Exception:
35+
pass # Ignore if key doesn't exist
36+
37+
# Publish listener address using RPUSH (list operation)
38+
# Dialer will use BLPOP to block and read this value
39+
self.redis_client.rpush(redis_key, actual_addr)
40+
41+
# Dialer: Wait for address
42+
blpop_result = self.redis_client.blpop(
43+
redis_key, timeout=remaining_timeout
44+
)
45+
if blpop_result:
46+
# BLPOP returns (key, value) tuple - extract the multiaddr string
47+
listener_addr = (
48+
blpop_result[1]
49+
if isinstance(blpop_result, (list, tuple))
50+
and len(blpop_result) > 1
51+
else blpop_result
52+
)
53+
```
54+
55+
**Files**:
56+
57+
- `transport/images/python/v0.x/py-libp2p/interop/transport/ping_test.py` (lines 737-755, 902-930)
58+
59+
### 2. Environment Variable Handling
60+
61+
**Implementation**: All environment variables use uppercase names only, matching the test framework specification. Variables are strictly required (throw errors if not set) or optional with defaults.
62+
63+
**Current Implementation**:
64+
65+
```python
66+
# Required variables (throw error if not set)
67+
self.transport = os.getenv("TRANSPORT")
68+
if not self.transport:
69+
raise ValueError("TRANSPORT environment variable is required")
70+
71+
self.redis_addr = os.getenv("REDIS_ADDR")
72+
if not self.redis_addr:
73+
raise ValueError("REDIS_ADDR environment variable is required")
74+
75+
self.ip = os.getenv("LISTENER_IP")
76+
if not self.ip:
77+
raise ValueError("LISTENER_IP environment variable is required")
78+
79+
self.test_key = os.getenv("TEST_KEY")
80+
if not self.test_key:
81+
raise ValueError("TEST_KEY environment variable is required")
82+
83+
is_dialer_value = os.getenv("IS_DIALER")
84+
if is_dialer_value is None:
85+
raise ValueError("IS_DIALER environment variable is required")
86+
self.is_dialer = is_dialer_value == "true" # Case-sensitive match
87+
88+
# Optional variables with defaults
89+
debug_value = os.getenv("DEBUG") or "false" # Optional, default to "false"
90+
91+
timeout_value = os.getenv("TEST_TIMEOUT_SECS") or "180"
92+
raw_timeout = int(timeout_value)
93+
self.test_timeout_seconds = min(raw_timeout, MAX_TEST_TIMEOUT)
94+
```
95+
96+
**Files**:
97+
98+
- `transport/images/python/v0.x/py-libp2p/interop/transport/ping_test.py` (lines 54-64, 94-150)
99+
100+
### 3. Standalone Transport Support
101+
102+
**Implementation**: Properly handles standalone transports (quic-v1) where `SECURE_CHANNEL` and `MUXER` environment variables are optional (not set by the test framework).
103+
104+
**Environment Variable Handling**:
105+
106+
```python
107+
# Standalone transports don't use separate security/muxer
108+
standalone_transports = ["quic-v1"] # Python currently only supports quic-v1
109+
110+
# Check if transport is standalone before requiring MUXER/SECURE_CHANNEL
111+
if self.transport not in standalone_transports:
112+
# Non-standalone transports: MUXER and SECURE_CHANNEL are required
113+
muxer_env = os.getenv("MUXER")
114+
if muxer_env is None:
115+
raise ValueError("MUXER environment variable is required")
116+
self.muxer = muxer_env
117+
118+
security_env = os.getenv("SECURE_CHANNEL")
119+
if security_env is None:
120+
raise ValueError("SECURE_CHANNEL environment variable is required")
121+
self.security = security_env
122+
else:
123+
# Standalone transports: MUXER and SECURE_CHANNEL are optional
124+
muxer_env = os.getenv("MUXER")
125+
self.muxer = muxer_env if muxer_env else None
126+
127+
security_env = os.getenv("SECURE_CHANNEL")
128+
self.security = security_env if security_env else None
129+
```
130+
131+
**Security Options for Standalone Transports**:
132+
133+
```python
134+
def create_security_options(self):
135+
"""Create security options based on configuration."""
136+
# Standalone transports have security built-in, no separate security needed
137+
standalone_transports = ["quic-v1"]
138+
if self.transport in standalone_transports:
139+
# For standalone transports, return empty security options
140+
# The security is handled by the transport itself
141+
key_pair = create_new_key_pair()
142+
return {}, key_pair
143+
144+
# Non-standalone transports: create security options as before
145+
key_pair = create_new_key_pair()
146+
147+
if self.security == "noise":
148+
# ... noise setup ...
149+
elif self.security == "tls":
150+
# ... tls setup ...
151+
elif self.security == "plaintext":
152+
# ... plaintext setup ...
153+
else:
154+
raise ValueError(f"Unsupported security: {self.security}")
155+
```
156+
157+
**Muxer Options for Standalone Transports**:
158+
159+
```python
160+
def create_muxer_options(self):
161+
"""Create muxer options based on configuration."""
162+
# Standalone transports have muxing built-in, no separate muxer needed
163+
standalone_transports = ["quic-v1"]
164+
if self.transport in standalone_transports:
165+
# For standalone transports, return None (no separate muxer)
166+
# The muxing is handled by the transport itself
167+
return None
168+
169+
# Non-standalone transports: create muxer options as before
170+
if self.muxer == "yamux":
171+
return create_yamux_muxer_option()
172+
elif self.muxer == "mplex":
173+
return create_mplex_muxer_option()
174+
else:
175+
raise ValueError(f"Unsupported muxer: {self.muxer}")
176+
```
177+
178+
**Files**:
179+
180+
- `transport/images/python/v0.x/py-libp2p/interop/transport/ping_test.py` (lines 98-119, 183-218)
181+
182+
### 4. Output Format
183+
184+
**Implementation**: Outputs YAML format to stdout as required by the transport test framework:
185+
186+
```python
187+
print("latency:", file=sys.stdout)
188+
print(f" handshake_plus_one_rtt: {handshake_plus_one_rtt}", file=sys.stdout)
189+
print(f" ping_rtt: {ping_rtt}", file=sys.stdout)
190+
print(" unit: ms", file=sys.stdout)
191+
```
192+
193+
**Files**:
194+
195+
- `transport/images/python/v0.x/py-libp2p/interop/transport/ping_test.py` (lines 1080-1086)
196+
197+
### 5. TEST_KEY Support
198+
199+
**Implementation**: Uses `TEST_KEY` environment variable for Redis key namespacing with strict validation:
200+
201+
```python
202+
self.test_key = os.getenv("TEST_KEY")
203+
if not self.test_key:
204+
raise ValueError("TEST_KEY environment variable is required")
205+
206+
redis_key = f"{self.test_key}_listener_multiaddr"
207+
```
208+
209+
**Files**:
210+
211+
- `transport/images/python/v0.x/py-libp2p/interop/transport/ping_test.py` (lines 147-150, 745, 906)
212+
213+
### 6. Code Quality and Error Handling
214+
215+
**Implementation**:
216+
217+
- Type hints: Uses Python type hints (`redis.Redis | None`, `INetStream`)
218+
- Error handling: Comprehensive error handling with descriptive messages
219+
- Redis connection: Retry mechanism with exponential backoff for Redis connections
220+
- Stream creation: Retry mechanism for stream creation to handle timing issues
221+
- Debug logging: Optional debug logging controlled by `DEBUG` environment variable
222+
- Validation: Configuration validation for transports, security, and muxers
223+
224+
**Files**:
225+
226+
- `transport/images/python/v0.x/py-libp2p/interop/transport/ping_test.py` (throughout)
227+
228+
## Testing
229+
230+
The Python v0.x implementation follows the transport test framework specification and is tested as part of the full test suite.
231+
232+
## Compatibility
233+
234+
- ✅ Follows transport test framework specification (`transport/README.md`)
235+
- ✅ Matches Redis protocol used by Rust and JavaScript implementations
236+
- ✅ Uses uppercase environment variables only (as set by test framework)
237+
- ✅ Consistent with JavaScript v3.x reference implementation
238+
- ✅ Supports standalone transports (quic-v1) with proper handling
239+
240+
## Supported Transports
241+
242+
- **Standard transports**: `tcp`, `ws`, `wss`
243+
- **Standalone transports**: `quic-v1` (with built-in security and muxing)
244+
245+
## Supported Security Channels
246+
247+
- **Noise**: Noise protocol for encryption
248+
- **TLS**: TLS protocol for encryption
249+
- **Plaintext**: Insecure transport (for testing)
250+
251+
## Supported Muxers
252+
253+
- **Yamux**: Yamux stream multiplexer
254+
- **Mplex**: Mplex stream multiplexer

0 commit comments

Comments
 (0)