From b0ffa28e461ba7c322d866cd20b5523d76a6f258 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Sat, 7 Mar 2026 19:06:19 -0800 Subject: [PATCH] Phase 4: Update advert path storage --- app/event_handlers.py | 10 ++++++---- app/packet_processor.py | 13 +++++++++++-- app/repository/contacts.py | 13 +++++++------ app/routers/contacts.py | 4 ++-- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/app/event_handlers.py b/app/event_handlers.py index 3eb3e18..6686d41 100644 --- a/app/event_handlers.py +++ b/app/event_handlers.py @@ -132,7 +132,11 @@ async def on_contact_message(event: "Event") -> None: logger.debug("DM from %s handled by event handler (fallback path)", sender_pubkey[:12]) # Build paths array for broadcast - paths = [MessagePath(path=path or "", received_at=received_at, path_len=path_len)] if path is not None else None + paths = ( + [MessagePath(path=path or "", received_at=received_at, path_len=path_len)] + if path is not None + else None + ) # Broadcast the new message broadcast_event( @@ -221,9 +225,7 @@ async def on_path_update(event: "Event") -> None: ) return - await ContactRepository.update_path( - contact.public_key, str(path), normalized_path_len - ) + await ContactRepository.update_path(contact.public_key, str(path), normalized_path_len) async def on_new_contact(event: "Event") -> None: diff --git a/app/packet_processor.py b/app/packet_processor.py index 0ea7feb..9928b59 100644 --- a/app/packet_processor.py +++ b/app/packet_processor.py @@ -196,7 +196,11 @@ async def create_message_from_decrypted( # Build paths array for broadcast # Use "is not None" to include empty string (direct/0-hop messages) - paths = [MessagePath(path=path or "", received_at=received, path_len=path_len)] if path is not None else None + paths = ( + [MessagePath(path=path or "", received_at=received, path_len=path_len)] + if path is not None + else None + ) # Broadcast new message to connected clients (and fanout modules when realtime) broadcast_event( @@ -305,7 +309,11 @@ async def create_dm_message_from_decrypted( await RawPacketRepository.mark_decrypted(packet_id, msg_id) # Build paths array for broadcast - paths = [MessagePath(path=path or "", received_at=received, path_len=path_len)] if path is not None else None + paths = ( + [MessagePath(path=path or "", received_at=received, path_len=path_len)] + if path is not None + else None + ) # Broadcast new message to connected clients (and fanout modules when realtime) sender_name = contact.name if contact and not outgoing else None @@ -709,6 +717,7 @@ async def _process_advertisement( path_hex=new_path_hex, timestamp=timestamp, max_paths=10, + hop_count=new_path_len, ) # Record name history diff --git a/app/repository/contacts.py b/app/repository/contacts.py index dbaf853..eb478ca 100644 --- a/app/repository/contacts.py +++ b/app/repository/contacts.py @@ -8,6 +8,7 @@ from app.models import ( ContactAdvertPathSummary, ContactNameHistory, ) +from app.path_utils import first_hop_hex class AmbiguousPublicKeyPrefixError(ValueError): @@ -200,9 +201,7 @@ class ContactRepository: return [ContactRepository._row_to_contact(row) for row in rows] @staticmethod - async def update_path( - public_key: str, path: str, path_len: int - ) -> None: + async def update_path(public_key: str, path: str, path_len: int) -> None: await db.conn.execute( """UPDATE contacts SET last_path = ?, last_path_len = ?, last_seen = ? WHERE public_key = ?""", @@ -290,10 +289,11 @@ class ContactAdvertPathRepository: @staticmethod def _row_to_path(row) -> ContactAdvertPath: path = row["path_hex"] or "" - next_hop = path[:2].lower() if len(path) >= 2 else None + path_len = row["path_len"] + next_hop = first_hop_hex(path, path_len) return ContactAdvertPath( path=path, - path_len=row["path_len"], + path_len=path_len, next_hop=next_hop, first_seen=row["first_seen"], last_seen=row["last_seen"], @@ -306,6 +306,7 @@ class ContactAdvertPathRepository: path_hex: str, timestamp: int, max_paths: int = 10, + hop_count: int | None = None, ) -> None: """ Upsert a unique advert path observation for a contact and prune to N most recent. @@ -315,7 +316,7 @@ class ContactAdvertPathRepository: normalized_key = public_key.lower() normalized_path = path_hex.lower() - path_len = len(normalized_path) // 2 + path_len = hop_count if hop_count is not None else len(normalized_path) // 2 await db.conn.execute( """ diff --git a/app/routers/contacts.py b/app/routers/contacts.py index 4744377..c5a5b6e 100644 --- a/app/routers/contacts.py +++ b/app/routers/contacts.py @@ -204,8 +204,8 @@ async def get_contact_detail(public_key: str) -> ContactDetail: # Compute nearest repeaters from first-hop prefixes in advert paths first_hop_stats: dict[str, dict] = {} # prefix -> {heard_count, path_len, last_seen} for p in advert_paths: - if p.path and len(p.path) >= 2: - prefix = p.path[:2].lower() + prefix = p.next_hop + if prefix: if prefix not in first_hop_stats: first_hop_stats[prefix] = { "heard_count": 0,