Add manifest consistency check and tests

This commit is contained in:
thePR0M3TH3AN
2025-07-24 20:05:30 -04:00
parent cf3803c422
commit cb1e18c8ba
2 changed files with 73 additions and 1 deletions

View File

@@ -368,6 +368,7 @@ class NostrClient:
start = time.perf_counter()
if self.offline_mode or not self.relays:
return Manifest(ver=1, algo="gzip", chunks=[]), ""
await self.ensure_manifest_is_current()
await self._connect_async()
manifest, chunks = prepare_snapshot(encrypted_bytes, limit)
for meta, chunk in zip(manifest.chunks, chunks):
@@ -537,10 +538,39 @@ class NostrClient:
return None
async def ensure_manifest_is_current(self) -> None:
"""Verify the local manifest is up to date before publishing."""
if self.offline_mode or not self.relays:
return
await self._connect_async()
pubkey = self.keys.public_key()
ident = f"{MANIFEST_ID_PREFIX}{self.fingerprint}"
f = Filter().author(pubkey).kind(Kind(KIND_MANIFEST)).identifier(ident).limit(1)
timeout = timedelta(seconds=10)
try:
events = (await self.client.fetch_events(f, timeout)).to_vec()
except Exception:
return
if not events:
return
try:
data = json.loads(events[0].content())
remote = data.get("delta_since")
if remote is not None:
remote = int(remote)
except Exception:
return
with self._state_lock:
local = self.current_manifest.delta_since if self.current_manifest else None
if remote is not None and (local is None or remote > local):
self.last_error = "Manifest out of date"
raise RuntimeError("Manifest out of date")
async def publish_delta(self, delta_bytes: bytes, manifest_id: str) -> str:
"""Publish a delta event referencing a manifest."""
if self.offline_mode or not self.relays:
return ""
await self.ensure_manifest_is_current()
await self._connect_async()
content = base64.b64encode(delta_bytes).decode("utf-8")

View File

@@ -1,8 +1,9 @@
import asyncio
import gzip
import math
import pytest
from helpers import create_vault, dummy_nostr_client
from helpers import create_vault, dummy_nostr_client, TEST_SEED
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
from seedpass.core.config_manager import ConfigManager
@@ -141,3 +142,44 @@ def test_fetch_snapshot_uses_event_ids(dummy_nostr_client):
if getattr(f, "kind_val", None) == KIND_SNAPSHOT_CHUNK
]
assert id_filters and all(id_filters)
def test_publish_delta_aborts_if_outdated(tmp_path, monkeypatch, dummy_nostr_client):
client1, relay = dummy_nostr_client
from cryptography.fernet import Fernet
from nostr.client import NostrClient
from seedpass.core.encryption import EncryptionManager
enc_mgr = EncryptionManager(Fernet.generate_key(), tmp_path)
class DummyKeys:
def private_key_hex(self):
return "1" * 64
def public_key_hex(self):
return "2" * 64
class DummyKeyManager:
def __init__(self, *a, **k):
self.keys = DummyKeys()
with pytest.MonkeyPatch().context() as mp:
mp.setattr("nostr.client.KeyManager", DummyKeyManager)
mp.setattr(enc_mgr, "decrypt_parent_seed", lambda: TEST_SEED)
client2 = NostrClient(enc_mgr, "fp")
base = b"base"
manifest, _ = asyncio.run(client1.publish_snapshot(base))
with client1._state_lock:
client1.current_manifest.delta_since = 0
import copy
with client2._state_lock:
client2.current_manifest = copy.deepcopy(manifest)
client2.current_manifest_id = manifest_id = relay.manifests[-1].tags[0]
asyncio.run(client2.publish_delta(b"d1", manifest_id))
with pytest.raises(RuntimeError):
asyncio.run(client1.publish_delta(b"d2", manifest_id))