macula_hyparview_proto (macula v11.4.0)

View Source

HyParView protocol orchestrator (Part 3 §7.1).

Pure functional layer on top of macula_hyparview_view. Given the current view, an incoming HyParView frame, and a context (self node_id, realm, ARWL/PRWL constants and the admission trust), process/4 returns the updated view plus a list of {send, TargetPeer, Frame} actions the wrapping process should transmit.

Frames leave this module without a signature of their own: the connection that sends a frame adds its neighbour signature (D17).

Message handlers

  • JOIN — receiver becomes the joiner's contact: add_active(joiner), reply NEIGHBOR(high), FORWARD_JOIN(ttl=ARWL) to every other active peer. Eviction in the active view emits DISCONNECT to the demoted peer. When ctx() carries realm_key_id, admission is gated on the frame's record field holding the wire form of a realm_member_endorsement that verifies under the ctx profile, signed by the realm key with that key id, for this exact (realm, joiner) pair (macula_hyparview_endorsement: verify_endorsement/3). A missing or invalid endorsement is dropped silently (no ack, no forward, view unchanged), never admitted. Without realm_key_id in ctx(), admission is unconditional (opt-in gating, matching the frame spec's own "endorsement is optional" design).
  • FORWARD_JOIN — if ttl=0 or active view is empty: add_active(new_member) + reply NEIGHBOR(high). Otherwise: if ttl equals this node's own PRWL, also place new_member in the passive view, within the sender's allowance; decrement ttl and forward to a random active peer that is not the sender. Carries the ORIGINAL JOIN's record (endorsement) through the whole forward chain and re-verifies it at every admission point — trust in the endorsement is never transitively assumed just because a neighbour forwarded it.
  • NEIGHBOR(high) — always add_active(sender), evicting if needed.
  • NEIGHBOR(low) — add_active(sender) only when the active view has room.
  • DISCONNECT — demote sender to passive.
  • SHUFFLE — if ttl > 0 and a forwardable neighbour exists, decrement ttl + forward; otherwise build SHUFFLE_REPLY against our own sample and send back to the origin, then place the incoming sample in the passive view, within the sender's allowance.
  • SHUFFLE_REPLY: place the incoming sample in the passive view, within the sender's allowance, only while a SHUFFLE this node sent in the last 30 seconds has no reply yet (build_shuffle/2 records each one).

A neighbour places at most 20 node_ids per minute in the passive view, counting only node_ids new to it (DESIGN_PQ_DHT_SLOTS_AND_BUDGET.md, 3.1). A frame that brings more, and a SHUFFLE_REPLY that answers no SHUFFLE, return a {refused, Neighbour, Kind} action, which the wrapping process reports through macula_peering:object_refused/2. ctx() carries now, in monotonic milliseconds, for both.

Reference: plans/PLAN_MACULA_V2_PART3_DISCOVERY.md §7.1; plans/PLAN_PHASE_5_BREAKDOWN.md Session 5.2.

Summary

Functions

Build a JOIN frame to send to a contact peer.

A periodic shuffle round: a SHUFFLE to a random active neighbour, carrying a sample of this node's view, and the view recording that it was sent, so that one SHUFFLE_REPLY within 30 seconds is merged. No SHUFFLE goes out while the active view is empty.

Types

action/0

-type action() ::
          {send, peer(), macula_frame:frame()} |
          {refused, peer(), placement_allowance | unsolicited_shuffle_reply}.

ctx/0

-type ctx() ::
          #{self_id := peer(),
            realm := <<_:256>>,
            now := integer(),
            arwl => non_neg_integer(),
            prwl => non_neg_integer(),
            shuffle_ttl => non_neg_integer(),
            shuffle_active_size => non_neg_integer(),
            shuffle_passive_size => non_neg_integer(),
            realm_key_id => <<_:256>>,
            profile => macula_crypto_profile:profile(),
            self_endorsement => binary()}.

peer/0

-type peer() :: <<_:256>>.

Functions

build_join(_)

-spec build_join(ctx()) -> macula_frame:frame().

Build a JOIN frame to send to a contact peer.

build_shuffle(View, Ctx)

-spec build_shuffle(macula_hyparview_view:view(), ctx()) -> {macula_hyparview_view:view(), [action()]}.

A periodic shuffle round: a SHUFFLE to a random active neighbour, carrying a sample of this node's view, and the view recording that it was sent, so that one SHUFFLE_REPLY within 30 seconds is merged. No SHUFFLE goes out while the active view is empty.

process(View, FromId, F, Ctx)