@le-space/rootfs 0.12.4 → 0.12.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/rootfs",
3
- "version": "0.12.4",
3
+ "version": "0.12.5",
4
4
  "description": "Shared rootfs contract parsing, reference profile assets, and build helpers.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -49,6 +49,18 @@ COMPACT_MULTIADDR_LIMIT = int(os.environ.get("ALEPH_BOOTSTRAP_COMPACT_MULTIADDR_
49
49
  STALE_RECORD_MAX_AGE_SECONDS = int(
50
50
  os.environ.get("ALEPH_BOOTSTRAP_STALE_RECORD_MAX_AGE_SECONDS", str(6 * 60 * 60))
51
51
  )
52
+ # What was last published, so an unchanged registration is not posted again.
53
+ # The AutoTLS refresh starts this unit after each of its runs, every 2 min until
54
+ # AutoTLS has a certificate; on a relay where it never gets one that meant a new
55
+ # Aleph post every 2 min, forever. An unchanged registration is republished only
56
+ # once it is this old, which keeps the 6 h timer's refresh (and stays below
57
+ # STALE_RECORD_MAX_AGE_SECONDS, after which consumers drop it).
58
+ PUBLISHED_STATE_FILE = os.environ.get(
59
+ "BOOTSTRAP_PUBLISHED_STATE_FILE", "/var/lib/orbitdb-relay/bootstrap-published.json"
60
+ )
61
+ REPUBLISH_UNCHANGED_AFTER_SECONDS = int(
62
+ os.environ.get("BOOTSTRAP_REPUBLISH_UNCHANGED_AFTER_SECONDS", str(5 * 60 * 60))
63
+ )
52
64
 
53
65
 
54
66
  def parse_env_file(path: str) -> dict[str, str]:
@@ -327,6 +339,61 @@ def resolve_owner_address(
327
339
  return None
328
340
 
329
341
 
342
+ def registration_fingerprint(
343
+ publisher_address: str,
344
+ peer_id: str,
345
+ multiaddrs: list[str],
346
+ registration_id: str | None,
347
+ profile: str,
348
+ version: str | None,
349
+ owner_address: str | None,
350
+ channel: str,
351
+ ref: str,
352
+ ) -> str:
353
+ """What a registration says, without its timestamps and signatures."""
354
+ return hashlib.sha256(
355
+ json_dumps(
356
+ {
357
+ "publisher": publisher_address.lower(),
358
+ "peerId": peer_id,
359
+ "multiaddrs": sorted(multiaddrs),
360
+ "registrationId": registration_id,
361
+ "profile": profile,
362
+ "version": version,
363
+ "owner": (owner_address or "").lower() or None,
364
+ "channel": channel,
365
+ "ref": ref,
366
+ }
367
+ ).encode("utf-8")
368
+ ).hexdigest()
369
+
370
+
371
+ def recently_published(fingerprint: str, now_seconds: float) -> bool:
372
+ try:
373
+ with open(PUBLISHED_STATE_FILE, encoding="utf-8") as handle:
374
+ state = json.load(handle)
375
+ except (OSError, ValueError):
376
+ return False
377
+ if not isinstance(state, dict) or state.get("fingerprint") != fingerprint:
378
+ return False
379
+ published_at = state.get("publishedAt")
380
+ if not isinstance(published_at, (int, float)):
381
+ return False
382
+ return 0 <= now_seconds - published_at < REPUBLISH_UNCHANGED_AFTER_SECONDS
383
+
384
+
385
+ def remember_published(fingerprint: str, item_hash: str, now_seconds: float) -> None:
386
+ try:
387
+ os.makedirs(os.path.dirname(PUBLISHED_STATE_FILE) or ".", exist_ok=True)
388
+ tmp = f"{PUBLISHED_STATE_FILE}.tmp"
389
+ with open(tmp, "w", encoding="utf-8") as handle:
390
+ json.dump({"fingerprint": fingerprint, "itemHash": item_hash, "publishedAt": now_seconds}, handle)
391
+ os.replace(tmp, PUBLISHED_STATE_FILE)
392
+ except OSError:
393
+ # Without the state the next run publishes again, as before.
394
+ pass
395
+
396
+
330
397
  def post_json(url: str, body: dict[str, object]) -> tuple[int, dict]:
331
398
  data = json_dumps(body).encode("utf-8")
332
399
  request = urllib.request.Request(
@@ -726,6 +793,26 @@ def attempt_publish(
726
793
  )
727
794
  owner_address = resolve_owner_address(env_values, owner_authorization)
728
795
 
796
+ fingerprint = registration_fingerprint(
797
+ publisher_address,
798
+ peer_id,
799
+ published_multiaddrs,
800
+ registration_id,
801
+ profile,
802
+ version,
803
+ owner_address,
804
+ channel,
805
+ ref,
806
+ )
807
+ if recently_published(fingerprint, now_seconds):
808
+ return {
809
+ "status": "unchanged",
810
+ "sender": publisher_address,
811
+ "peerId": peer_id,
812
+ "publishedMultiaddrs": published_multiaddrs,
813
+ "republishAfterSeconds": REPUBLISH_UNCHANGED_AFTER_SECONDS,
814
+ }
815
+
729
816
  proof_payload = relay_proof_payload(
730
817
  peer_id,
731
818
  published_multiaddrs,
@@ -762,6 +849,8 @@ def attempt_publish(
762
849
  )
763
850
  signed_message = sign_aleph_message(unsigned_message, publisher_private_key)
764
851
  http_status, response = broadcast_aleph_message(api_host, signed_message)
852
+ if 200 <= http_status < 300:
853
+ remember_published(fingerprint, str(unsigned_message["item_hash"]), now_seconds)
765
854
 
766
855
  previous_hashes = fetch_previous_hashes(
767
856
  api_host,