erldistpy/docs/ROADMAP.md

2.5 KiB

erldistpy roadmap

Phases below are sized to land as discrete commits. Each phase ends with make all green and a real-VM interop test where applicable.

Phase 0 — Repo bones

  • LICENSE, README, .gitignore, Makefile, pyproject.toml
  • Package skeleton, ruff config matches unfeed
  • make bootstrap test lint works on a fresh checkout

Phase 1 — ETF codec

  • Subset of External Term Format we need for gen_call to Elixir: small_int, int, big_int, atom_utf8 (legacy atom_ext on decode), binary, nil, list, tuple (small + large), pid (new_pid), ref (newer_reference)
  • Booleans and None ride as atoms true / false / nil
  • Golden vectors decoded from real term_to_binary/1 output
  • Round-trip tests for the encoder
  • Generator script committed at docs/etf_vectors.erl

Phase 2 — EPMD client

EPMD (Erlang Port Mapper Daemon) maps node names to TCP ports.

  • Synchronous TCP client
  • PORT_PLEASE2_REQ (122) → EpmdInfo(name, port, node_type, protocol, hi_ver, lo_ver, extra)
  • Returns None cleanly on unregistered node, raises EpmdError on socket failures
  • Tests: recorded byte streams from real EPMD + live integration that spawns its own erl -sname and tears it down

Phase 3 — Distribution handshake

  • TCP connect to the resolved port
  • send_name / recv_status / recv_challenge / send_challenge_reply / recv_challenge_ack
  • Cookie digest via erlang:phash2-equivalent (md5-based per spec)
  • Version 6 ("v6") handshake, the modern one Elixir 1.15+ uses
  • Tests: handshake against a live Erlang node started in conftest

Phase 4 — Distribution channel

  • After handshake, the socket carries control + payload messages framed by a 4-byte length prefix
  • Send SEND_TT / REG_SEND for outgoing messages
  • Receive replies, route by ref
  • Tick loop for keepalive (60s default per OTP)

Phase 5 — gen_call convenience layer

  • Node.call(name_or_pid, request, timeout=5.0) → reply term
  • Wraps the $gen_call protocol used by :gen_server
  • Idempotency-key handling lives in the caller; we just pass the term

Phase 6 — TLS dist

  • Wrap the post-EPMD socket in TLS
  • Match inet_tls_dist config on the Erlang side (cert + key + ca paths)
  • Same handshake, just runs inside the TLS tunnel

Phase 7 — unfeed integration

  • ErlangDistTransport implementing unfeed's WalletTransport Protocol
  • Maps /v1/health, /v1/address, ... to gen_call requests
  • unfeed deploys with a config switch: wallet_transport = http | erldist
  • Cutover after a soak period on staging