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 ("\n Ready 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 )
0 commit comments