67 lines
2.5 KiB
Markdown
67 lines
2.5 KiB
Markdown
# 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
|