# Changelog
All notable changes to this project are documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to the 0.x semantics described in the README
(a minor bump may break).
## [0.1.0] - 2026-05-26
First public release. Barrel P2P is an enhancement to Erlang distribution:
a `proto_dist` module over QUIC with HyParView membership, Plumtree gossip,
a CRDT service registry, and Ed25519 peer authentication, while keeping
`Pid ! Msg`, `gen_server`, `rpc`, `global`, links, and monitors working
normally.
### Membership and gossip
- HyParView partial-view membership: bounded active/passive views, ARWL/PRWL
forwarding, shuffle, neighbor swap, age-based passive cleanup, churn
handling, and a backstop timer (`pending_timeout_ms`, default 30s) so a peer
that goes silent mid-handshake never leaks a pending entry.
- Plumtree epidemic broadcast: eager/lazy push with ihave/graft/prune and
self-healing; peers are removed from both eager and lazy sets on `peer_down`.
- Membership event subscription: `barrel_p2p:subscribe/0,1`, `unsubscribe/1`
deliver `{barrel_p2p_event, {peer_up, Node} | {peer_down, Node, Reason}}`.
### Distribution carrier
- `-proto_dist barrel_p2p`: a transparent boot shim over upstream `quic_dist`.
One QUIC connection per peer carries the Erlang dist channel, multiplexed
across a pool of streams (the control stream is prioritised). Three vm.args
flags select it; certificate paths, the auth callback, and discovery are
projected into `quic_dist` automatically.
- Dist channels are decoupled from the HyParView active view: `Pid ! Msg`
works between any cluster members via OTP's demand-driven auto-connect,
resolved through the discovery chain. The active view tracks only the
bounded gossip topology.
- EPMD-less by default (`barrel_p2p_epmd`).
- Composing discovery chain (`barrel_p2p_discovery`): static config
(`barrel_p2p_discovery_static`), a shared on-disk registry
(`barrel_p2p_discovery_file`), and DNS host fallback
(`barrel_p2p_discovery_dns`).
- Config-driven seeding: `contact_nodes` auto-joins the listed seeds at boot
(`barrel_p2p_bootstrap`), retrying every `contact_retry_ms` (default 5000)
until the node is in the overlay, with no manual `barrel_p2p:join/1`.
- Idle dist-channel GC (`barrel_p2p_dist_gc`): an always-on reaper that drops
channels not in the active view, carrying no live user stream, and aged
past `dist_gc_min_age_ms`.
- Connection migration: `barrel_p2p:migrate_peer/1,2` triggers RFC 9000 ยง9 path
migration on a peer's dist channel, rebinding to a new local 4-tuple without
rekey or HyParView churn.
- Pluggable transport seam: `quic_dist:set_connect_options/2` routes a peer
through an out-of-tree relay/tunnel adapter (MASQUE, WireGuard, SSH
ProxyCommand). Experimental; no committed adapter.
### Authentication and identity
- Ed25519 mutual authentication after the QUIC TLS handshake and before the
Erlang dist handshake. The signed message is bound to the QUIC TLS channel
(a SHA-256 of the server cert) and to the responder's own public key, so a
relayed handshake lands on a different cert and fails: this closes an
on-path MITM in both trust modes. Wire protocol v2.
- TOFU (default) and strict trust modes; a pinned node is rejected if it
presents a different key in either mode (no silent re-pin). Fingerprint-keyed
trust store on disk; `cookie_only_nodes` whitelist for probes that cannot
speak the auth protocol (symmetric check).
- The dist handshake carries the claimed node name as a binary and mints the
atom only after the signature verifies, closing an atom-table exhaustion DoS
reachable before authentication. Names are format- and length-validated
(255-byte cap, `name@host` shape, restricted charset).
- Self-signed QUIC TLS cert is ECDSA P-256 with `notBefore` backdated for peer
clock skew, a CSPRNG 127-bit serial, and GeneralizedTime encoding for
validity years >= 2050.
- Secret material (`node.key`, trust-store pins) is written through
`barrel_p2p_file:write_secure/2`: chmod 0600 before any plaintext byte, then
atomic rename. The keypair is consistency-checked on load
(`{error, keypair_mismatch}` rather than using mismatched material).
- Boot guards: `auth_enabled` defaults to `true`; boot warns on the default
cookie, on `auth_enabled = false`, and on accepting a cookie-only peer, and
refuses to start when `cookie_only_nodes` is set with the default cookie or
when `auth_enabled = true` with an `undefined` auth callback.
- Handshake enforces a wall deadline across all recv sites; the replay-window
check is responder-side monotonic with a cross-host wall-clock sanity bound,
so an NTP step mid-handshake cannot spuriously fail.
- `barrel_p2p_rotate:rotate_cert/0,1` and `rotate_identity/0,1`: atomic backup
under `<dir>/backups/<UTC-timestamp>/`. Identity rotation takes effect on the
next handshake; cert rotation requires a restart.
### Service registry
- CRDT (Observed-Remove Map) registry replicated through the gossip layer:
`register_service/1,2,3`, `unregister_service/1`, `lookup/1`,
`lookup_local/1`, `list_services/0`, `whereis_service/1,2` with overlay-routed
fallback, and local-pid proxies for remote services.
- OTP `via` callbacks (`{via, barrel_p2p, Name}`), `global_register/1`, and
`get_proxy/1`.
- Service events: `subscribe_services/0,1`, `unsubscribe_services/1` deliver
`{barrel_p2p_service_event, {service_registered | service_unregistered, Name,
Node} | {service_down, Name, Node, Reason}}`.
- Overlay routing is bounded: `barrel_p2p_router` caps concurrent in-flight
handlers (`router_max_in_flight`, default 256; over-cap replies
`{error, overloaded}`), relays carry a TTL and visited list to prevent
ping-pong, and a periodic sweep (`route_cache_sweep_period_ms`) evicts stale
cache entries. `barrel_p2p_service_proxy` bounds overlay casts
(`proxy_cast_max_in_flight`, default 32) and reaps dead remote proxies.
### Replicated state and coordination
- `barrel_p2p_replica`: a public behaviour for replicated state with custom
merge or snapshot semantics. Gossiped OR-Map deltas, full-sync on `peer_up`,
prune on `peer_down`, and seed-from-active-view plus pull-on-start so an
instance created after the cluster formed recovers existing state. Callbacks
take the instance name first, so one module backs many named instances.
Periodic anti-entropy (`replica_anti_entropy_ms`, default 30000, `0`
disables) reconverges value-carrying stores after a partition heal even
without a fresh `peer_up`.
- `barrel_p2p_crdt_wire`: supported helper for safe gossip ingest (wrapper
validation plus an optional leaf check; guards non-map payloads). The
registry, leader, shard, and reminder validate incoming gossip before
merging.
- `barrel_p2p_map`: replicated last-write-wins maps for small cluster-wide
control-plane state. `new_map/1,2`, `delete_map/1`, `map_put/3`,
`map_remove/2`, `map_get/2`, `map_keys/1`, `map_to_list/1`,
`subscribe_map/1,2`, `unsubscribe_map/1,2`. One owner gen_server per map with
a lock-free ETS read cache; per-map `validator`, `tombstone_ttl_ms`,
`scan_ms`, `prune_on_peer_down`, and opt-in `persist => true`. Beta.
- Durable reminders (`barrel_p2p_reminder`): `remind/3`, `remind_after/3`,
`cancel_reminder/1`, `subscribe_reminders/0,1`. Replicated, disk-persisted,
fire-at-most-once timers that survive the node that armed them; the owner is
`barrel_p2p:place/1`, so a survivor fires after the owner dies. Delivery is
`{barrel_p2p_reminder, Key, Payload, Fence}` with a stable fence for idempotent
dedup. Beta.
- Sharded placement (`barrel_p2p_shard`): `place/1`, `owners/2`, `is_owner/1`,
`partition/1`, `members/0`, `subscribe_shard/0,1`. Rendezvous (HRW) hashing
over a replicated, lease-based live-node set (periodic heartbeats), bucketed
into `ring_size` partitions; owners react to
`{barrel_p2p_shard, {acquired | released, Partition}}`. Beta.
- Leader election / singletons (`barrel_p2p_leader`): `lead/1,2`, `resign/1`,
`leader/1`, `is_leader/1`, `fence/1`. A process campaigns for a named
singleton and is notified with `{barrel_p2p_leader, Name, {elected, Fence} |
revoked}`; the cluster elects one leader (highest priority, ties to lowest
node atom) and re-elects on churn. Each term carries an HLC fencing token,
strictly monotonic within a connected partition. Beta.
- Disk persistence (`barrel_p2p_replica_log`): a write-ahead log plus periodic
snapshots, recovered on boot. Durable reminders survive a full-cluster
restart (a `remind`/`cancel` is flushed before it returns); maps opt in with
`persist => true`. Persisted values must be restart-safe data (no
pids/ports/refs/funs). Config: `reminder_data_dir`, `barrel_p2p_map_data_dir`.
- Hybrid Logical Clocks (`barrel_p2p_hlc`) for causally-ordered timestamps used
by the CRDT and coordination layers.
### Streams
- Tagged user-stream multiplex (`barrel_p2p_streams`): one acceptor per tag,
demultiplexed by a `<<TagLen:8, Tag/binary>>` preamble, handed to the
acceptor process which then owns the stream. Independent QUIC flow control
per stream; inbound streams awaiting a preamble are capped (64) to bound a
hostile peer. For bulk byte transfer where message passing is the wrong
shape. Beta.
### Observability
- `barrel_p2p_metrics`: counters and histograms emitted through the `instrument`
library at the HyParView, dist-auth, Plumtree, GC, router, service-proxy,
streams, and migrate seams. Cached in `persistent_term`; emit sites stay off
the hot path.
- `barrel_p2p_path_stats`: `summary/1`, `srtt/1`, `connection/1` over upstream
`quic:get_path_stats/1`, resolving a peer node to its QUIC connection pid.
### Tooling
- `priv/bin/barrel_p2p_call.sh`: an `erl_call`-style one-shot RPC helper that
boots a hidden probe with a full Ed25519 identity and runs `rpc:call` against
a live node.
- `priv/bin/barrel_p2p_gen_cert.sh`: a self-signed cert generator for the QUIC
dist channel (`--out-dir`, `--cn`, `--days`, `--key-bits`, `--force`;
idempotent).
### Tests and CI
- Property-based tests (PropEr) for the OR-Map CRDT laws, HLC monotonicity and
binary round-trip, dist-protocol encode/decode plus fuzz survival, and the
stream demuxer under random fragmentation.
- EUnit and Common Test suites covering membership, registry (incl. via
callbacks and the global bridge), Plumtree, dist auth, two/three-node
cluster mechanics, the `-proto_dist barrel_p2p` boot path, maps, reminders,
leader election, sharded placement, and end-to-end convergence and
partition-heal scenarios. A gated soak suite (`BARREL_P2P_CT_SOAK=1`) and a
bench harness (`bench/run.sh`) round these out.
- GitHub Actions matrix on OTP 27 and 28: compile, xref, dialyzer, EUnit,
Common Test, lint (elvis), formatting (erlfmt), and a bench gate.
Dialyzer- and xref-clean.
### Documentation
- A documentation tree under `docs/` (overview, concepts, tutorials including
a from-scratch quickstart, how-to guides including key management and
production, and reference including the configuration list, the replicated
substrate, and a Partisan comparison), published via `ex_doc`. Runnable
examples under `examples/`.
- LICENSE (Apache-2.0) and SECURITY.md.
[0.1.0]: https://github.com/barrel-platform/barrel_p2p/releases/tag/v0.1.0