Skip to content

v0.11.0: relay reliability metric, DHT fallback, call remote control, wiring into browser and headless node

Choose a tag to compare

@github-actions github-actions released this 20 Oct 15:31
· 7 commits to master since this release

Feature release: a relay reliability metric with automatic relay selection, a DHT fallback for the peer directory, prekey bundles, mail and direct messages, DM and call acceptance gates, and a browser page that remote-controls voice calls. Since the wiring wave (V0.9.15) the browser node and a new headless CLI node switch these on behind operator options (table below). With the defaults, a node differs from v0.10.1 in three ways: the DHT fallback is on (see the metadata warning), the browser publishes its own record and prekey bundle again whenever its addresses change, and there is a status panel and the call page.

Relay reliability and automatic relay selection (V0.9.12).

  • Madli daily vector wire format v2 with an optional "relay reliability" block. v1 vectors stay byte-identical (pinned by a golden test generated on v0.10.1); a vector without the block is always v1. Old nodes reject v2 vectors as invalid, so the publisher MadliRelayReliabilityPublisher is off by default (enabled = false) and only the explicit option publish-relay-reliability=true switches it on.
  • Relay usage ledger (local, never gossiped): bytes forwarded per circuit are charged to the receiving side and credited to the relay. A peer with a negative balance is deferred in the top capacity band (priorityHeadroomFraction, default 0.25); reputation is never a hard gate.
  • Automatic relay selection (LapisNode.startAutoRelay, two relays per node) is active only when the operator declares RelayConfig.reachability = PRIVATE. The reachability heuristic only warns on a mismatch; it never changes behaviour. The IP classifier is hardened and non-public candidate addresses are refused by default.
  • Wired (V0.9.15): with reachability=private (and auto-relay not set to false) the browser and lapisnet node start the selector, ranked by Madli reliability weighted by the local Veritas graph; the node's own record is re-published as soon as the /p2p-circuit addresses exist. The record never advertises RELAY: this node does not serve as a relay. The publisher is off by default and is switched on only by publish-relay-reliability=true (it then publishes the previous UTC day once per day); nothing writes v2 vectors unless an operator asks for it.

DHT fallback for the directory (V0.9.13) and for mail and direct messages (V0.9.14).

  • Hybrid: GossipSub stays the fast path and its validators are unchanged. Peer records and prekey bundles are additionally published as one IPNS value per identity, served by an own IPNS PUT_VALUE/GET_VALUE responder and validator (Nabu's cannot serve Ed25519 names) with a bounded record store.
  • Mail and direct-message pointers are left in an outbox inside that value. A rendezvous provider record lets a recipient find senders that have something for it, and a fetcher applies the same acceptance checks as the gossip path before it fetches the body. Plaintext mail deliberately leaves no outbox pointer, only encrypted mail does, so plaintext mail is never recoverable from the DHT.
  • Wired (V0.9.15): the browser publishes its record, prekey bundle and outbox to the DHT (at most every 5 minutes, and with a reduced bundle because the value must stay small), hands the outbox to the mail and DM senders, runs discoverInbox, refreshStale and a catch-up over its accepted contacts every minute (the library throttles apply) and starts the mail fetcher. The pointer of an offline DM from the DHT passes MailboxGossip.dhtConsumer(), i.e. the same acceptance as the gossip validator; registerOutboxConsumer refuses a second consumer, so there is no second, unchecked entrance. lapisnet node publishes only a peer record without any capability (no DM, no mail in the CLI). --dht-fallback=false turns all of it off.

Call remote control in the browser (V0.8.7b).

  • /call.html remote-controls a call whose audio runs inside the node's JVM: incoming-call card (Decline before Accept), active call with timer, place-call form, call history. State arrives over Server-Sent Events.
  • Guarded as a local-only page: exact Host check, Origin and Sec-Fetch-Site checks, JSON-only bodies of at most 4 KiB, a per-process session token exchanged for an HttpOnly cookie, no CORS, at most 4 event subscribers. The call history is kept in memory only.
  • The acceptance gates are wired (V0.9.15, off by default): one DmAcceptanceCheck instance goes to both MailboxGossip (pre-check, gossip and DHT path) and DmSessionManager (classification after decryption). A call from a sender who passes no active gate is dropped silently: the caller receives neither RINGING nor REJECT (a rejection would confirm the node is online and reward probing). The call page shows a muted "blocked" row with the reason (Veritas below threshold, Karma below threshold, or the generic acceptance rule); the reason is computed locally and never sent. POST /api/dm/<peer>/accept now really lifts the gates for that peer and is guarded like the call routes (loopback Host, same-origin Origin). There is no mute.

Operator options (V0.9.15). The same keys everywhere; an invalid value in any source aborts the start (browser: exit code 2 and one line on stderr), it never falls back to a default. Priority in the browser: command-line arguments, LAPISNET_* environment variables, $LAPISNET_HOME/browser.properties (at most 64 KiB), defaults. The CLI takes arguments only.

  • --reachability= (reachability, LAPISNET_REACHABILITY): public, private or undeclared; default undeclared; never guessed.
  • --auto-relay= (autoRelay, LAPISNET_AUTO_RELAY): true or false; default true if and only if reachability is private; true requires --reachability=private.
  • --dht-fallback= (dhtFallback, LAPISNET_DHT_FALLBACK): true or false; default true.
  • --publish-relay-reliability= (publishRelayReliability, LAPISNET_PUBLISH_RELAY_RELIABILITY): true or false; default false; older nodes reject v2 vectors as invalid.
  • --dm-veritas-gate= (dmAcceptance.veritasGate, LAPISNET_DM_VERITAS_GATE): true or false; default false.
  • --dm-karma-min-score= (dmAcceptance.karmaMinScore, LAPISNET_DM_KARMA_MIN_SCORE): a finite number >= 0; default off.
  • --dm-min-deposit-msat= (dmAcceptance.minDepositMsat, LAPISNET_DM_MIN_DEPOSIT_MSAT): an integer >= 1; default 1000000.

Unknown keys, repeated keys and bare words are errors too. lapisnet node --help and lapisnet-browser --help print the options. reachability=private with auto-relay=false declares the node private but runs no selector (and the library's reachability warning, which sees UNSPECIFIED, is then replaced by one INFO line). The node's status, including the active gates, is shown read-only on the start page (GET /api/node/status); a gate cannot be edited there.

New CLI subcommand. lapisnet node [options] runs a lean headless node: relay selection, DHT directory fallback and Madli, but no DM and no mail (the CLI has no inbox, no session store). lapisnet without arguments still runs the demo.

Provisional defaults. These choices were made without an explicit decision of the maintainers and are open to change: the DHT fallback is on by default; auto-relay follows the declared reachability; the publisher is off; all acceptance gates are off (an empty trust graph would otherwise block every stranger silently); a per-identity Karma score (not defined by lapis-net-karma) is the sum of the Veritas-weighted personalised Karma of the sender's 20 most recent posts; a missing Lightning deposit is not reported as its own block reason (the deposit is a bypass, not a gate), so every block that is not exactly one gate reads "acceptance rule".

Known limits.

  • DHT lookups reveal to the peers that are asked who is looking for whom - query timing and key interest in inbox, prekey and directory keys - until traffic anonymisation (section 11 of docs/architecture.adoc) exists; switch the fallback off with --dht-fallback=false. Pointers in the DHT make this exposure persistent for up to seven days instead of only while a gossip topic is live.
  • The rendezvous key of a victim can be eclipsed by throwaway peer identities (a known Kademlia weakness, not mitigated); contacts already in the directory are not affected.
  • The mail acceptance (MailAcceptanceCheck) is not wired: it behaves identically on the gossip and the DHT path (both unset), so there is no bypass, but there is no mail filter either. The accepted-contacts set, the outbox and the relay usage ledger are held in memory only, and a new identity starts with a balance of 0.
  • Still no hole punching (dcutr): relayed connections stay relayed, and voice calls need a direct connection.
  • Test note: MultiNodeIpnsValueTest ("acknowledged counts the echoes that carry the record, not the requests that were sent") failed in about 1 of 18 runs under heavy CPU load (8 to 16 busy loops on two cores): a peer was missing from the routing table and attempted was 2 instead of 3. The cause is not understood, so a red run of that test alone is not necessarily a regression.
  • The downloads are not signed. The new waves had only the pipeline's own review and security loops, no independent audit.

Downloads. lapis-net-browser-0.11.0.zip and lapis-net-cli-0.11.0.zip need a JDK 25 on the PATH or in JAVA_HOME; unpack and run bin/lapis-net-browser or bin/lapis-net-cli. SHA256SUMS lists the checksum of each file. The artifacts are not signed: the checksums only detect corrupted downloads, not a compromised release account. See https://github.com/lapisproject-dev/Lapis-Net/blob/master/docs/releasing.adoc for how to verify them.