Skip to content

Commit 0b71f6e

Browse files
authored
Merge pull request #3 from paschal533/py-libp2p-en-workshop
Initialize py-libp2p Universal Connectivity Workshop
2 parents 29950c3 + a178b41 commit 0b71f6e

Some content is hidden

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

68 files changed

+7181
-1
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
FROM python:3.9-slim AS builder
2+
3+
WORKDIR /app
4+
5+
# Install build dependencies
6+
RUN apt-get update && apt-get install -y \
7+
build-essential \
8+
libssl-dev \
9+
libffi-dev \
10+
&& rm -rf /var/lib/apt/lists/*
11+
12+
# Copy requirements and install Python dependencies
13+
COPY requirements.txt* ./
14+
RUN if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
15+
16+
# Install common dependencies for the workshop
17+
RUN pip install cryptography base58 aiohttp
18+
19+
# Copy the application
20+
COPY . .
21+
22+
# Final stage
23+
FROM python:3.9-slim
24+
25+
# Install runtime dependencies
26+
RUN apt-get update && apt-get install -y \
27+
libssl-dev \
28+
&& rm -rf /var/lib/apt/lists/*
29+
30+
WORKDIR /app
31+
32+
# Copy Python packages from builder
33+
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
34+
COPY --from=builder /usr/local/bin /usr/local/bin
35+
36+
# Copy the application
37+
COPY . .
38+
39+
# Configurable timeout duration
40+
ARG TIMEOUT_DURATION=10s
41+
ENV TIMEOUT_DURATION=${TIMEOUT_DURATION}
42+
43+
# Set the command to run with timeout and redirect output
44+
CMD ["/bin/sh", "-c", "timeout ${TIMEOUT_DURATION} python app/main.py > stdout.log 2>&1"]

en/py/01-identity-and-swarm/app/main.py

Whitespace-only changes.
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Check script for Lesson 1: Identity and Basic Host
4+
Validates that the student's solution creates a libp2p host with identity.
5+
"""
6+
7+
import subprocess
8+
import sys
9+
import os
10+
import re
11+
import base58
12+
13+
def validate_peer_id(peer_id_str):
14+
"""Validate that the peer ID string is a valid base58 format"""
15+
try:
16+
# Try to decode the peer ID as base58
17+
decoded = base58.b58decode(peer_id_str)
18+
19+
# Should be 32 bytes (SHA256 hash length)
20+
if len(decoded) != 32:
21+
return False, f"Invalid peer ID length. Expected 32 bytes, got {len(decoded)}: {peer_id_str}"
22+
23+
# Check if it's a valid base58 string (no invalid characters)
24+
re_encoded = base58.b58encode(decoded).decode('ascii')
25+
if re_encoded != peer_id_str:
26+
return False, f"Peer ID base58 encoding is inconsistent: {peer_id_str}"
27+
28+
return True, f"Valid peer ID format: {peer_id_str}"
29+
30+
except Exception as e:
31+
return False, f"Invalid peer ID format: {peer_id_str} - Error: {e}"
32+
33+
def check_output():
34+
"""Check the output log for expected content"""
35+
if not os.path.exists("stdout.log"):
36+
print("X Error: stdout.log file not found")
37+
return False
38+
39+
try:
40+
with open("stdout.log", "r") as f:
41+
output = f.read()
42+
43+
print("i Checking application output...")
44+
45+
if not output.strip():
46+
print("X stdout.log is empty - application may have failed to start")
47+
return False
48+
49+
# Check for startup message
50+
if "Starting Universal Connectivity Application" not in output:
51+
print("X Missing startup message. Expected: 'Starting Universal Connectivity Application...'")
52+
print(f"i Actual output: {repr(output[:200])}")
53+
return False
54+
print("v Found startup message")
55+
56+
# Check for peer ID output
57+
peer_id_pattern = r"Local peer id: ([A-Za-z0-9]+)"
58+
peer_id_match = re.search(peer_id_pattern, output)
59+
60+
if not peer_id_match:
61+
print("X Missing peer ID output. Expected format: 'Local peer id: <base58_string>'")
62+
print(f"i Actual output: {repr(output[:200])}")
63+
return False
64+
65+
peer_id = peer_id_match.group(1)
66+
67+
# Validate the peer ID format
68+
valid, message = validate_peer_id(peer_id)
69+
if not valid:
70+
print(f"X {message}")
71+
return False
72+
73+
print(f"v {message}")
74+
75+
# Check for host startup message
76+
if "Host started with PeerId:" not in output:
77+
print("X Missing host startup message. Expected: 'Host started with PeerId: ...'")
78+
print(f"i Actual output: {repr(output[:200])}")
79+
return False
80+
print("v Found host startup message")
81+
82+
# Check that the application doesn't crash immediately
83+
lines = output.strip().split('\n')
84+
if len(lines) < 3:
85+
print("X Application seems to have crashed immediately after startup")
86+
print(f"i Output lines: {lines}")
87+
return False
88+
89+
print("v Application started successfully and generated valid peer identity")
90+
return True
91+
92+
except Exception as e:
93+
print(f"X Error reading stdout.log: {e}")
94+
return False
95+
96+
def check_code_structure():
97+
"""Check if the code has the expected structure"""
98+
app_file = "app/main.py"
99+
100+
if not os.path.exists(app_file):
101+
print("X Error: app/main.py file not found")
102+
return False
103+
104+
try:
105+
with open(app_file, "r") as f:
106+
code = f.read()
107+
108+
print("i Checking code structure...")
109+
110+
# Check for required imports
111+
required_imports = [
112+
"trio",
113+
"ed25519",
114+
"base58"
115+
]
116+
117+
for imp in required_imports:
118+
if imp not in code:
119+
print(f"X Missing import: {imp}")
120+
return False
121+
print("v Required imports found")
122+
123+
# Check for LibP2PHost class
124+
if "class LibP2PHost" not in code:
125+
print("X Missing LibP2PHost class definition")
126+
return False
127+
print("v LibP2PHost class found")
128+
129+
# Check for async main function
130+
if "async def main" not in code:
131+
print("X Missing async main function")
132+
return False
133+
print("v Async main function found")
134+
135+
# Check for key generation
136+
if "Ed25519PrivateKey.generate()" not in code:
137+
print("X Missing Ed25519 key generation")
138+
return False
139+
print("v Ed25519 key generation found")
140+
141+
# Check for PeerId creation
142+
if "base58.b58encode" not in code:
143+
print("X Missing PeerId base58 encoding")
144+
return False
145+
print("v PeerId creation found")
146+
147+
print("v Code structure is correct")
148+
return True
149+
150+
except Exception as e:
151+
print(f"X Error reading code file: {e}")
152+
return False
153+
154+
def main():
155+
"""Main check function"""
156+
print("Checking Lesson 1: Identity and Basic Host")
157+
print("=" * 60)
158+
159+
try:
160+
# Check code structure first
161+
if not check_code_structure():
162+
return False
163+
164+
# Check the output
165+
if not check_output():
166+
return False
167+
168+
print("=" * 60)
169+
print("All checks passed! Your libp2p host is working correctly.")
170+
print("v You have successfully:")
171+
print(" * Created a libp2p host with a stable Ed25519 identity")
172+
print(" * Generated and displayed a valid peer ID")
173+
print(" * Set up a basic async event loop")
174+
print(" * Implemented proper host lifecycle management")
175+
print("\nReady for Lesson 2: Transport and Multiaddrs!")
176+
177+
return True
178+
179+
except Exception as e:
180+
print(f"X Unexpected error during checking: {e}")
181+
return False
182+
183+
if __name__ == "__main__":
184+
success = main()
185+
sys.exit(0 if success else 1)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
services:
2+
lesson:
3+
build:
4+
context: ${PROJECT_ROOT}
5+
dockerfile: ${LESSON_PATH}/app/Dockerfile
6+
container_name: workshop-lesson
7+
stop_grace_period: 1m
8+
environment:
9+
- TIMEOUT_DURATION=${TIMEOUT_DURATION:-10s}
10+
- REMOTE_PEERS=${REMOTE_PEERS:-/ip4/172.16.16.17/tcp/9092}
11+
volumes:
12+
- ${PROJECT_ROOT}/${LESSON_PATH}/stdout.log:/app/stdout.log
13+
networks:
14+
workshop-net:
15+
ipv4_address: 172.16.16.16
16+
17+
networks:
18+
workshop-net:
19+
name: workshop-net
20+
external: true
21+
driver: bridge
22+
ipam:
23+
config:
24+
- subnet: 172.16.16.0/24

0 commit comments

Comments
 (0)