mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Merge pull request #676 from PR0M3TH3AN/codex/add-manifest-version-check-before-publishing
Add manifest consistency check before publishing
This commit is contained in:
@@ -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")
|
||||
|
@@ -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))
|
||||
|
Reference in New Issue
Block a user