Changelog
View SourceAll notable changes to this project are documented in this file. The format follows Keep a Changelog, 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/1deliver{barrel_p2p_event, {peer_up, Node} | {peer_down, Node, Reason}}.
Distribution carrier
-proto_dist barrel_p2p: a transparent boot shim over upstreamquic_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 intoquic_distautomatically.- Dist channels are decoupled from the HyParView active view:
Pid ! Msgworks 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_nodesauto-joins the listed seeds at boot (barrel_p2p_bootstrap), retrying everycontact_retry_ms(default 5000) until the node is in the overlay, with no manualbarrel_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 pastdist_gc_min_age_ms. - Connection migration:
barrel_p2p:migrate_peer/1,2triggers 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/2routes 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_nodeswhitelist 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@hostshape, restricted charset). - Self-signed QUIC TLS cert is ECDSA P-256 with
notBeforebackdated 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 throughbarrel_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_enableddefaults totrue; boot warns on the default cookie, onauth_enabled = false, and on accepting a cookie-only peer, and refuses to start whencookie_only_nodesis set with the default cookie or whenauth_enabled = truewith anundefinedauth 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,1androtate_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,2with overlay-routed fallback, and local-pid proxies for remote services. - OTP
viacallbacks ({via, barrel_p2p, Name}),global_register/1, andget_proxy/1. - Service events:
subscribe_services/0,1,unsubscribe_services/1deliver{barrel_p2p_service_event, {service_registered | service_unregistered, Name, Node} | {service_down, Name, Node, Reason}}. - Overlay routing is bounded:
barrel_p2p_routercaps 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_proxybounds 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 onpeer_up, prune onpeer_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,0disables) reconverges value-carrying stores after a partition heal even without a freshpeer_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-mapvalidator,tombstone_ttl_ms,scan_ms,prune_on_peer_down, and opt-inpersist => 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 isbarrel_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 intoring_sizepartitions; 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 (aremind/cancelis flushed before it returns); maps opt in withpersist => 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 theinstrumentlibrary at the HyParView, dist-auth, Plumtree, GC, router, service-proxy, streams, and migrate seams. Cached inpersistent_term; emit sites stay off the hot path.barrel_p2p_path_stats:summary/1,srtt/1,connection/1over upstreamquic:get_path_stats/1, resolving a peer node to its QUIC connection pid.
Tooling
priv/bin/barrel_p2p_call.sh: anerl_call-style one-shot RPC helper that boots a hidden probe with a full Ed25519 identity and runsrpc:callagainst 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_p2pboot 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 viaex_doc. Runnable examples underexamples/. - LICENSE (Apache-2.0) and SECURITY.md.