diff --git a/repeater/main.py b/repeater/main.py index 22629c9..ab0a206 100644 --- a/repeater/main.py +++ b/repeater/main.py @@ -1,5 +1,6 @@ import asyncio import functools +import inspect import logging import os import signal @@ -780,6 +781,17 @@ class RepeaterDaemon: limit=retention or 100, ) loaded_messages = len(message_rows) + # openhop_core < the sender_prefix change (paired with fd43d86) has no + # QueuedMessage.sender_prefix; drop the prefix there instead of failing init. + supports_sender_prefix = "sender_prefix" in inspect.signature(QueuedMessage).parameters + if message_rows and not supports_sender_prefix: + logger.warning( + "Companion %s ('%s'): installed openhop_core QueuedMessage has no " + "sender_prefix field; persisted sender prefixes will be dropped " + "(update openhop_core to restore signed room-post authors)", + companion_hash_str, + name, + ) for msg_dict in message_rows: sk = msg_dict.get("sender_key", b"") if isinstance(sk, str): @@ -787,18 +799,18 @@ class RepeaterDaemon: sp = msg_dict.get("sender_prefix", b"") if isinstance(sp, str): sp = bytes.fromhex(sp) if sp else b"" - bridge.message_queue.push( - QueuedMessage( - sender_key=sk, - txt_type=msg_dict.get("txt_type", 0), - timestamp=msg_dict.get("timestamp", 0), - text=msg_dict.get("text", ""), - is_channel=bool(msg_dict.get("is_channel", False)), - channel_idx=msg_dict.get("channel_idx", 0), - path_len=msg_dict.get("path_len", 0), - sender_prefix=sp, - ) + msg_kwargs = dict( + sender_key=sk, + txt_type=msg_dict.get("txt_type", 0), + timestamp=msg_dict.get("timestamp", 0), + text=msg_dict.get("text", ""), + is_channel=bool(msg_dict.get("is_channel", False)), + channel_idx=msg_dict.get("channel_idx", 0), + path_len=msg_dict.get("path_len", 0), ) + if supports_sender_prefix: + msg_kwargs["sender_prefix"] = sp + bridge.message_queue.push(QueuedMessage(**msg_kwargs)) logger.info( "Companion %s ('%s'): restored %d/%d contact(s), %d/%d channel(s), " diff --git a/tests/test_companion_state_load.py b/tests/test_companion_state_load.py index a79d548..e30f5a8 100644 --- a/tests/test_companion_state_load.py +++ b/tests/test_companion_state_load.py @@ -128,6 +128,41 @@ class TestRestoreCompanionState: await daemon._restore_companion_state(sqlite, bridge, _HASH, _NAME) assert any("rejected persisted channel" in r.message for r in caplog.records) + @pytest.mark.asyncio + async def test_old_core_without_sender_prefix_drops_prefix(self, caplog): + # openhop_core releases before the sender_prefix change reject the kwarg; + # message restore must degrade (drop the prefix) instead of failing init. + from dataclasses import dataclass, field + + @dataclass + class LegacyQueuedMessage: + sender_key: bytes = b"" + txt_type: int = 0 + timestamp: int = 0 + text: str = "" + is_channel: bool = False + channel_idx: int = 0 + path_len: int = 0 + snr: float = 0.0 + rssi: int = 0 + channel_data_payload: bytes = field(default=b"") + + daemon = self._daemon() + bridge = self._bridge() + sqlite = self._sqlite( + messages=[{"sender_key": b"", "text": "hi", "sender_prefix": b"\xab\xcd"}] + ) + with ( + patch("openhop_core.companion.models.QueuedMessage", LegacyQueuedMessage), + caplog.at_level(logging.WARNING), + ): + await daemon._restore_companion_state(sqlite, bridge, _HASH, _NAME) + bridge.message_queue.push.assert_called_once() + pushed = bridge.message_queue.push.call_args[0][0] + assert isinstance(pushed, LegacyQueuedMessage) + assert pushed.text == "hi" + assert any("sender prefixes will be dropped" in r.message for r in caplog.records) + @pytest.mark.asyncio async def test_zero_retention_skips_message_load(self): daemon = self._daemon()