fix: persist sender_prefix for signed room posts

TXT_TYPE_SIGNED_PLAIN room posts carry a 4-byte author pubkey prefix
(QueuedMessage.sender_prefix, added in openhop_core). The SQLite
persistence path dropped it, so posts replayed from persistence showed
a zero-padded author prefix in the app frame while posts synced from
the live in-memory queue were correct.

- add sender_prefix column (hex text, default '') to companion_messages
  with an ALTER TABLE migration for existing databases
- store/return the prefix in companion_push/pop/load_messages
- pass sender_prefix when rebuilding QueuedMessage from persistence in
  the frame server and the startup preload paths
- add round-trip, default, migration, and rebuild tests
This commit is contained in:
agessaman
2026-07-06 22:46:49 -07:00
parent 700a38b8c1
commit fd43d86ea8
4 changed files with 163 additions and 5 deletions
+40 -5
View File
@@ -426,6 +426,7 @@ class SQLiteHandler:
is_channel INTEGER NOT NULL DEFAULT 0,
channel_idx INTEGER NOT NULL DEFAULT 0,
path_len INTEGER NOT NULL DEFAULT 0,
sender_prefix TEXT NOT NULL DEFAULT '',
packet_hash TEXT,
created_at REAL NOT NULL
)
@@ -581,6 +582,30 @@ class SQLiteHandler:
)
logger.info(f"Migration '{migration_name}' applied successfully")
# Migration 10: Add sender_prefix column (hex text) to
# companion_messages. TXT_TYPE_SIGNED_PLAIN room posts carry a
# 4-byte author pubkey prefix; without it, posts replayed from
# SQLite show a zero-padded author in the app frame.
migration_name = "add_sender_prefix_to_companion_messages"
existing = conn.execute(
"SELECT migration_name FROM migrations WHERE migration_name = ?",
(migration_name,),
).fetchone()
if not existing:
cursor = conn.execute("PRAGMA table_info(companion_messages)")
columns = [column[1] for column in cursor.fetchall()]
if "sender_prefix" not in columns:
conn.execute(
"ALTER TABLE companion_messages "
"ADD COLUMN sender_prefix TEXT NOT NULL DEFAULT ''"
)
logger.info("Added sender_prefix column to companion_messages table")
conn.execute(
"INSERT INTO migrations (migration_name, applied_at) VALUES (?, ?)",
(migration_name, time.time()),
)
logger.info(f"Migration '{migration_name}' applied successfully")
conn.commit()
except Exception as e:
@@ -2652,13 +2677,17 @@ class SQLiteHandler:
conn.row_factory = sqlite3.Row
cursor = conn.execute(
"""
SELECT sender_key, txt_type, timestamp, text, is_channel, channel_idx, path_len
SELECT sender_key, txt_type, timestamp, text, is_channel, channel_idx,
path_len, sender_prefix
FROM companion_messages WHERE companion_hash = ?
ORDER BY created_at ASC LIMIT ?
""",
(companion_hash, limit),
)
return [dict(row) for row in cursor.fetchall()]
rows = [dict(row) for row in cursor.fetchall()]
for msg in rows:
msg["sender_prefix"] = bytes.fromhex(msg.get("sender_prefix") or "")
return rows
except Exception as e:
logger.error(f"Failed to load companion messages: {e}")
return []
@@ -2683,13 +2712,16 @@ class SQLiteHandler:
if isinstance(packet_hash, bytes):
packet_hash = packet_hash.decode("utf-8", errors="replace") if packet_hash else None
sender_key = msg.get("sender_key", b"")
sender_prefix = msg.get("sender_prefix", b"")
if not isinstance(sender_prefix, str):
sender_prefix = bytes(sender_prefix or b"").hex()
with self._connect() as conn:
cursor = conn.execute(
"""
INSERT OR IGNORE INTO companion_messages
(companion_hash, sender_key, txt_type, timestamp, text,
is_channel, channel_idx, path_len, packet_hash, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
is_channel, channel_idx, path_len, sender_prefix, packet_hash, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
companion_hash,
@@ -2700,6 +2732,7 @@ class SQLiteHandler:
int(msg.get("is_channel", False)),
msg.get("channel_idx", 0),
msg.get("path_len", 0),
sender_prefix,
packet_hash,
time.time(),
),
@@ -2732,7 +2765,8 @@ class SQLiteHandler:
conn.row_factory = sqlite3.Row
cursor = conn.execute(
"""
SELECT id, sender_key, txt_type, timestamp, text, is_channel, channel_idx, path_len
SELECT id, sender_key, txt_type, timestamp, text, is_channel, channel_idx,
path_len, sender_prefix
FROM companion_messages WHERE companion_hash = ?
ORDER BY created_at ASC LIMIT 1
""",
@@ -2742,6 +2776,7 @@ class SQLiteHandler:
if not row:
return None
msg = dict(row)
msg["sender_prefix"] = bytes.fromhex(msg.get("sender_prefix") or "")
conn.execute("DELETE FROM companion_messages WHERE id = ?", (msg["id"],))
conn.commit()
return {k: v for k, v in msg.items() if k != "id"}