node-shared/infrastructure/ and is shared by all node types (DAG L0, DAG L1, Currency L0, Currency L1).
Cluster membership
Joining protocol (two-way handshake)
Joining is orchestrated bydomain/cluster/programs/Joining.scala. The protocol is a verified two-way handshake:
Validate join conditions
The joining node checks its own
NodeState to confirm it is allowed to join (e.g., ReadyToJoin). A session is created via session.createSession.Fetch registration request
The joiner calls
signClient.getRegistrationRequest on the target peer to obtain its RegistrationRequest (ID, IP, port, cluster session, software version, environment).Validate handshake
The joiner verifies:
- Software version hash matches
- Metagraph version hash matches
AppEnvironmentmatches (Dev / Testnet / Mainnet)- Cluster ID matches
- Cluster session token matches (or is not yet set)
- Peer is on the seedlist
- Allowance list matches
Sign challenge
A UUID-based
SignRequest is sent to the peer. The peer signs it and returns Signed[SignRequest]. The joiner verifies the signature against the peer’s declared ID.Send join request
The joiner sends its own
JoinRequest to the peer, which runs the same handshake in the opposite direction (skipJoinRequest = true to avoid infinite recursion).ClusterStorage and peer tracking
ClusterStorage is the in-memory registry of all known peers:
- Stores
Peerrecords indexed byPeerId - Exposes a
peerChangesstream (fs2.Stream[F, Ior[Peer, Peer]]) that other subsystems subscribe to (e.g.,GossipDaemonuses it to detect when the firstReadypeer appears before starting gossip). - Tracks
responsivePeers— peers that have passed liveness checks.
Gossip protocol
Tessellation uses an anti-entropy gossip protocol to propagate state changes across the cluster. The protocol is implemented inGossipDaemon and operates two independent round runners.
Two types of rumors
Peer rumors
Origin-specific messages from a single node. Each peer’s rumors are sequenced with consecutive ordinals (
Ordinal(generation, counter)). A peer rumor is only accepted if its counter is exactly one greater than the last accepted counter for that origin (addPeerRumorIfConsecutive). This ensures ordered, exactly-once delivery per origin.Common rumors
Content-addressed by SHA-256 hash. Any node may produce a common rumor and it is deduplicated globally by hash. Common rumors are accepted if they have not been seen before (
addCommonRumorIfUnseen). There is no ordering requirement between common rumors.Gossip round mechanics
TheGossipDaemon runs two concurrent round loops:
Peer rumor round (peerRound):
- Fetch the last known ordinals for each peer from
RumorStorage. - Compute
nextOrdinals = lastOrdinals.mapValues(o => Ordinal(o.generation, o.counter.next)). - Query a randomly selected peer for rumors starting at those ordinals.
- Enqueue received rumors for validation and handling.
commonRound):
- Request the remote peer’s set of active common rumor hashes (
getCommonRumorOffer). - Diff against locally seen hashes.
- Fetch the missing rumors (
queryCommonRumors). - Enqueue for validation and handling.
Collateral verification
Before a rumor is processed, the gossip daemon verifies that all signers of the rumor have sufficient collateral staked. Rumors from nodes without collateral are silently discarded.GossipDaemon, RumorStorage, RumorHandler
GossipDaemon
startAsRegularValidator waits for the first Ready peer to appear in ClusterStorage, initialises rumor storage from that peer, then starts both round runners and the rumor consumer.
RumorStorage
In-memory store backed byRef and MapRef (from Cats Effect):
| Method | Description |
|---|---|
addPeerRumorIfConsecutive | Accepts peer rumor only if ordinal is exactly next; returns AddSuccess, CounterTooHigh, CounterTooLow, or GenerationTooLow |
addCommonRumorIfUnseen | Accepts common rumor if its hash has not been seen; maintains LRU-bounded seen/active sets |
getPeerRumorsFromCursor | Returns rumors for a peer starting from a given ordinal cursor |
getCommonRumorSeenHashes | Returns the full set of hashes seen (used in exchange during round) |
RumorHandler (Kleisli-based)
Handlers areKleisli[OptionT[F, *], (RumorRaw, PeerId), Unit] functions — they return OptionT so that multiple handlers can be composed with <+> (SemigroupK) and the first matching handler wins:
TypeTag — no runtime class casting.
Peer authentication
All P2P HTTP endpoints are protected byPeerAuthMiddleware, which validates that incoming requests carry a valid signature from a known peer:
- The middleware extracts the peer’s
Idfrom the request headers. - It verifies the ECDSA signature over the request using the peer’s public key (derived from
Id). - Requests from peers not in
ClusterStorageor with invalid signatures are rejected.
httpSignerCore / httpSignerHttp4s libraries, which intercept the HTTP4s request pipeline and attach the node’s signature headers before sending.
Anti-entropy: state synchronisation
The gossip protocol acts as the anti-entropy mechanism for Tessellation:- New node joins —
startAsRegularValidatorfetches initial peer and common rumors from the first discoveredReadypeer, seeding itsRumorStoragewith current state. - Ongoing sync — Round runners continuously exchange rumor ordinals and hashes with randomly chosen peers, pulling any missing entries.
- Consensus declarations — Each consensus phase declaration (facility, proposal, signature) is spread as a rumor, ensuring all facilitators receive all declarations even under partial network partitions.
- Stall recovery — When the consensus monitor detects a stall,
spreadAckIfCollectingre-broadcasts acknowledgement rumors to any peers that have not yet responded.
The gossip layer is responsible for propagating consensus declarations between facilitators. If gossip delivery is delayed, the stall detector will re-spread acks after
declarationTimeout elapses.