Skip to content

Signed listings: every c0mpute disk lists itself, NAT or not - #6

Merged
ralyodio merged 1 commit into
mainfrom
feat/signed-node-listings
Nov 9, 2026
Merged

ralyodio merged 1 commit into
mainfrom
feat/signed-node-listings

Conversation

@ralyodio

@ralyodio ralyodio commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

What

Disks can now push their own listing instead of waiting to be fetched, so c0mpute nodes behind NAT (nearly all of them) show up on d1sks.

  • POST /api/v1/disks/announce with { "payload": "<JSON text>", "signature": "<hex ed25519>" }, payload type c0mpute.disk.announce/1 (marketplace, peer_id, key, ts, expires_in, origin, descriptor).
  • Verified, not fetched: peer_id must be the identity multihash of key, the signature must hold over the exact payload text, marketplace must be one of this site's hosts (ANNOUNCE_HOSTS, default d1sks.com, www, d1sks.pwa.pit.world plus SITE_URL), ts within 10 min and newer than the last one stored (replays get 409).
  • Validation: OpenDisk 0.1 rules plus caps (payload 24 KiB, free/total up to 1 PiB, free not above total, price up to $10/GiB-month, name 120 chars, at most 16 multiaddrs, https-only origin).
  • Rate limits: one accepted heartbeat per key per minute (429 + retry-after), 600 per address per hour, 50 new keys per address per day. Every attempt is logged in announces (pruned after 2 days).
  • Rows: source (origin or signed), peer_id, heartbeat_at, expires_at, reachable (https, direct, c0mpute), addrs. A signed row expires to gone when heartbeats stop (checked every minute, and active lists filter on expires_at too). The hourly poll only touches origin rows.
  • API: reachable=https|direct|c0mpute|public, fresh=<minutes>, source=signed|origin, sort=heartbeat; rows carry heartbeat_age_s for c0mpute hosting publish --disks auto ranking.
  • Pages: NAT disks read "reachable via c0mpute (behind NAT, no direct URL)", with last heartbeat and multiaddrs; docs and llms.txt describe the format.

Origin-based listings (POST /api/v1/disks {"url": ...}) are unchanged.

Tests

  • test/announce.test.js: verifies a listing signed by the Rust node code (fixture from opendisk::signed_listing), tampering, wrong peer, other marketplace, stale/future clocks, insane numbers, too many addrs, http origin, reachability.
  • test/announce.api.test.js (Postgres): list a NAT disk, replay 409, per-key 429, bad signature 401, new-keys-per-address cap, expiry to gone and back.
  • Test files no longer close the shared pool (Bun runs them in one process, so a later file was silently skipping its DB tests).

Companion: profullstack/c0mpute PR (nodes send the heartbeat every 5 min).

🤖 Generated with Claude Code

…unce/1)

POST /api/v1/disks/announce takes {payload, signature}: the descriptor signed
with the disk's ed25519 libp2p key. d1sks checks peer id = key, the signature,
the marketplace host, ts freshness and monotonicity, and sane numbers; rate
limits per key (1/min), per address (600/h) and new keys per address (50/day).
Rows keep source=signed, heartbeat_at, expires_at, reachable (https, direct,
c0mpute) and addrs; they expire to gone when heartbeats stop. Listing filters
reachable=, fresh=, source= and sort=heartbeat; rows carry heartbeat_age_s.
Pages show NAT disks as reachable via c0mpute with no direct URL. Origin
listings and the hourly poll are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ralyodio
ralyodio merged commit b9971a1 into main Nov 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant