-
Notifications
You must be signed in to change notification settings - Fork 54
Description
Summary
src/lean_spec/subspecs/networking/discovery/service.py is at 61% coverage (86 uncovered statements, 9 uncovered branches). The existing tests cover construction and static methods but miss all the async networking logic — the core of the Discovery v5 service.
Coverage stats:
| File | Statements | Missed | Branches | Br. Missed | Coverage |
|---|---|---|---|---|---|
discovery/service.py |
258 | 86 | 92 | 9 | 61% |
Existing test file: tests/lean_spec/subspecs/networking/discovery/test_service.py (49 tests passing)
What's already covered
- Service construction and configuration
- Some static/sync helper methods
What needs testing
1. find_node() — Kademlia iterative lookup (lines 204-239)
This is the core discovery algorithm and is completely untested.
- Happy path: Lookup finds nodes at target distance, returns sorted
LookupResult - No initial candidates: Returns empty result when routing table has no close nodes
- Parallel querying: Verify
LOOKUP_PARALLELISMcandidates are queried concurrently - Iterative deepening: Newly discovered nodes feed into subsequent rounds
- Result ordering: Final result sorted by XOR distance to target, capped at
K_BUCKET_SIZE
2. _query_node() — FINDNODE query (lines 358-362)
- Normal distance: Sends FINDNODE with computed log2 distance
- Zero distance: Falls back to distances
[1, 2, 3] - Return format: Returns
(enr_list, node_id, distances)tuple
3. _ping_node() — Ping and bond (lines 367-370)
- Successful pong: Adds bond to cache, returns
True - No response: Returns
False, no bond added
4. _handle_message() / _process_message() — Incoming message dispatch (lines 379, 389-397)
- Message routing: Each discovery message type dispatched to correct handler
- Unknown message: Handled gracefully
5. FINDNODE handler — Routing table ENR responses (lines 480-481)
- Distance 0: Returns own ENR
- Distance 1-256: Returns ENRs from corresponding routing table buckets
- Response size limit: Capped at
max_nodes_response
6. Background loops (lines 553-583)
_refresh_loop(): Performs random-target lookups periodically_revalidation_loop(): Pings random node, removes on failure_cleanup_loop(): Callsbond_cache.cleanup_expired()- Error resilience: All loops catch and log exceptions without crashing
7. _process_discovered_enr() — ENR processing and validation (lines 652-704)
This is security-critical and completely untested.
- Valid ENR: Parsed, added to routing table and seen dict, address registered
- Invalid ENR: Logged and skipped (missing required fields)
- No node ID: Logged and skipped
- Distance mismatch: Dropped (prevents routing table poisoning from malicious peers)
- Own ENR: Skipped (don't add self to routing table)
- Already seen: Skipped (dedup within a single lookup)
- Parse failure:
ValueErrorcaught and logged - Unexpected error: Generic exception caught and logged
Why this matters
- Core protocol logic:
find_node()is the fundamental peer discovery algorithm — if it's broken, nodes can't find each other - Security properties:
_process_discovered_enr()implements anti-poisoning checks (distance verification). Without tests, a regression could allow routing table attacks - Async correctness: The background loops and parallel queries involve subtle concurrency. Timing bugs are easy to introduce and hard to catch without tests
- Network resilience: Error handling in all async paths must be tested to ensure the service doesn't crash on malformed responses
How to test
Running tests with coverage
uv run pytest tests/lean_spec/subspecs/networking/discovery/test_service.py -v \
--cov=src/lean_spec/subspecs/networking/discovery/service --cov-report=term-missingTarget: ≥90% line and branch coverage (up from 61%).
Verifying with the coverage gate
uvx tox -e coverage-gateTesting tips
- Mock
_transport: The transport layer (send_findnode,send_ping,send_response,get_node_address,register_enr,register_node_address) should be mocked to isolate service logic from UDP I/O - Mock
_routing_table: Control whatclosest_nodes()andnodes_at_distance()return - Use
pytest-asynciofor all async tests - Background loops: Use short intervals or
asyncio.wait_forwith timeout to test loop behavior without waiting for real timers - ENR fixtures: Create valid/invalid
ENRobjects for_process_discovered_enr()tests. Check existing discovery test fixtures for patterns - The existing
test_service.pyalready has 49 tests — extend it rather than creating a new file
Using Claude Code subagents
If you're tackling this issue with Claude Code, use these agents for an efficient workflow:
1. code-tester agent — Generate the tests
Use this agent to scaffold the missing coverage. Prompt example:
Add tests to
tests/lean_spec/subspecs/networking/discovery/test_service.pyto cover the uncovered async methods. Focus on:find_node()iterative lookup,_query_node(),_ping_node(),_process_discovered_enr()validation logic (distance mismatch, invalid ENR, own ENR, dedup), and the three background loops (_refresh_loop,_revalidation_loop,_cleanup_loop). Mock the transport and routing table. Use pytest-asyncio.
2. doc-writer agent — Document new test groups
After tests are written, use this agent to add documentation explaining the testing strategy. Prompt example:
Add documentation to the new test classes explaining what security properties are being validated, especially for
_process_discovered_enr()anti-poisoning checks and the Kademlia lookup algorithm.
Workflow
- Read existing
test_service.pyto understand current patterns and fixtures - Run
code-testerto generate tests for uncovered methods - Run coverage, iterate until ≥90%
- Run
doc-writerto document new test groups - Run
uvx tox -e all-checksto pass all quality checks