Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Composition: how organisms wire together

audience: contributors

Architecture maps the six organisms onto a lattice. Organisms specifies each organism’s public surface. This chapter shows the wiring: which stream and collection subscription drives which organism’s state machine, and where the happy path splits when something upstream fails.

The wiring is intentionally weak. No cross-Group atomicity, no shared state, no global scheduler. Each organism reacts to its upstream’s public commits via mosaik’s when() DSL and Collection / Stream subscriptions. That is the whole composition model.

The slot as foreign key

Every commit in every organism is keyed by a slot — the chain’s slot number (on L1, the proposer slot; on L2, the sequencer’s block or sub-block index). The slot is the only shared identifier across organisms’ state machines. This is deliberate:

  • It is a small, dense, monotonic integer. Cheap to carry in every commit.
  • It is picked by the chain, not by any lattice organism. No organism can unilaterally fabricate or reorder slots.
  • It is a natural synchronization point: every organism has a well-defined answer to “what slot are we working on?”

Contributors writing new organisms should reuse the slot as the composition key. Composing on anything else — transaction hashes, bundle IDs, block numbers — breaks when chains reorg, re-propose, or split.

The subscription graph

Each arrow is a subscription: the downstream organism watches the upstream organism’s public collection or stream and reacts in its own state machine. No arrow represents atomic cross-Group commit.

      integrators
      (wallets,      ┌─► zipnet:Broadcasts ────► unseal:UnsealedPool ──┐
       searchers)    │                                                 │
         │           │                                                 ├─► atelier:Candidates ──► relay:AcceptedHeaders ──► tally:Refunds
         ├──► zipnet:Submit                                            │                                                          │
         │                                                             │                                                          │
         └──► offer:Bid ────────► offer:AuctionOutcome ─────────────┘                                                          │
                                                                                                                                     │
                                                     on-chain inclusion watcher ──────────────────────────────────────────────┘
                                                                                                                                     │
                                                                                                                          tally:Attestations ──► integrators

Read top to bottom, left to right:

  1. Wallets submit to zipnet:Submit; searchers submit to offer:Bid. Both are ticket-gated writes onto the lattice’s public surface.
  2. zipnet finalizes a round and appends to Broadcasts.
  3. unseal watches zipnet:Broadcasts, combines threshold shares, and writes cleartext into UnsealedPool.
  4. offer watches UnsealedPool for slot boundaries, runs its sealed-bid auction over bundles that reference the pool, and commits AuctionOutcome.
  5. atelier watches both UnsealedPool and AuctionOutcome, assembles a candidate block in the TDX committee, and commits Candidates.
  6. relay watches Candidates, ships headers to the proposer, and commits AcceptedHeaders when the proposer acknowledges.
  7. An on-chain inclusion watcher watches the chain; when the winning block lands, tally commits Refunds and publishes Attestations.
  8. Integrators read Refunds and Attestations to claim their share.

Subscription code shape

A contributor implementing one organism writes a role driver in that organism’s crate. The driver is just a mosaik event loop that watches an upstream primitive and calls group.execute(...) on its own Group. The pattern is identical across organisms; the unseal driver is the simplest and serves as the template:

// Inside unseal::node::roles::server
loop {
    // Watch zipnet's public Broadcasts collection for the next
    // finalized round.
    let next = broadcasts.when().appended().await;
    let round = broadcasts.get(next).expect("appended implies present");

    // Compute this node's threshold share for the round.
    let share = compute_share(&self.share_secret, &round);

    // Commit into unseal's own Group.
    self.unseal_group
        .execute(UnsealCommand::SubmitShare { slot: round.slot, share })
        .await?;
}

atelier’s driver is the same shape over UnsealedPool + AuctionOutcome. tally’s is the same shape over AcceptedHeaders + an on-chain watcher. In every case the driver ends at group.execute(...) on its own state machine; the upstream is read-only.

Apply order across organisms

Within one organism, mosaik’s Raft variant guarantees that every committee member applies commands in the same order. Across organisms, no such guarantee exists — each organism’s state machine runs independently. The lattice relies on two properties instead:

  1. Monotonicity by slot. Within any organism, commits for slot S+1 are never applied before commits for slot S. The organism’s own state machine enforces this in its apply handler by rejecting out-of-order slots.
  2. Eventual consistency by subscription. A downstream organism’s driver will observe every upstream commit eventually, because mosaik collections are append-only and readers converge. It may observe them out of wall-clock order across organisms, but each organism’s state machine processes them in slot order regardless.

The two properties together are enough to reconstruct a globally consistent view per slot without global atomicity. Integrators wanting “the canonical decision for slot S” read each organism’s per-slot commit independently and join on the slot number.

What happens when an upstream organism fails

Each row below covers one organism failing or stalling. “Fails” means its committee cannot commit within a slot’s deadline. The columns are the downstream organisms’ observable behaviour.

Upstream failszipnetunsealofferatelierrelaytally
zipnet-no UnsealedPool[S]AuctionOutcome[S] still commits (bids still valid)Candidates[S] degrades (no order flow, searcher bids only)AcceptedHeaders[S] still possibleRefunds[S] attribution set may be empty for slot
unsealunaffected-AuctionOutcome[S] still commitsCandidates[S] degrades (bids only)AcceptedHeaders[S] still possibleRefunds[S] attribution set missing wallet contributions
offerunaffectedunaffected-Candidates[S] degrades (no bid included)AcceptedHeaders[S] still possibleRefunds[S] missing searcher attributions
atelierunaffectedunaffectedunaffected-no Candidates[S] to ship; proposer falls back to another builderno Refunds[S] committed
relayunaffectedunaffectedunaffectedCandidates[S] commits fine-no Refunds[S] if block never reaches chain
tallyunaffectedunaffectedunaffectedunaffectedunaffected-

Two patterns fall out:

  • Upstream failures degrade downstream outputs; they do not corrupt them. A missing unseal for slot S produces a Candidates[S] built from searcher bids alone, which is still a well-formed block, just with less order-flow content. The chain still progresses via the proposer’s fallback builder.
  • The pipeline is drainable. Failures during slot S do not block slot S+1 — every organism’s state machine accepts new slots without waiting for earlier ones to finalise.

What the composition contract guarantees

Given that every organism commits its own decision for slot S, the lattice guarantees:

  • Deterministic replay. Given the full commit logs of all six organisms for slot S, anyone can recompute every per-organism decision and cross-check tally’s attribution.
  • Independent auditability. A consumer that trusts the chain can verify tally’s attestations against on-chain inclusion without trusting any other organism’s commit log directly — the attestation carries the evidence.
  • No silent corruption across organisms. The derived id discipline means mis-configured organisms in a lattice produce disjoint IDs and cannot cross-subscribe at all. A half-upgraded lattice is always a ConnectTimeout, never a subtle mis-attribution.

What the contract does not guarantee

  • Atomic all-or-nothing inclusion across organisms. Already discussed. A partial path through the pipeline is a valid state.
  • Bounded end-to-end latency in the presence of failures. If relay stalls indefinitely, tally’s commit for that slot never happens; no organism-level timeout triggers it. Operators who need bounded end-to-end latency add per-slot deadlines at the tally level (commit an empty Refunds[S] after a timeout rather than blocking).
  • Cross-lattice coordination. Out of scope for this chapter; see Cross-lattice coordination.

Contributors implementing a new organism

If you are adding a seventh organism to the lattice, your wiring checklist is:

  1. Identify the upstream organism(s) you subscribe to. If none — you are a root organism like zipnet, triggered only by integrator input.
  2. Identify the downstream organism(s) that will subscribe to you. If none — you are a leaf like tally, triggered only by external observers.
  3. Key every commit by slot. If your organism has a natural sub-slot cadence (per-round inside a slot, per-bundle), commit at the sub-slot cadence but always stamp the owning slot.
  4. Write one role driver per Group member role. Keep it as a single tokio::select! over the upstream subscriptions and your local timers.
  5. Write unit tests against in-memory collections (mosaik ships test helpers); integration tests against an in-process lattice of two or three organism Groups.
  6. Document the subscription contract in your organism’s contributors/composition-hooks.md. Update organisms.md and this page’s subscription graph to include the new organism.

Cross-references

  • Architecture — the lattice shape these subscriptions run on.
  • The six organisms — each organism’s own public surface that this page’s arrows point at.
  • Threat model — how trust assumptions compose across the subscription graph.
  • Cross-lattice coordination — what happens when the subscription graph crosses a lattice boundary.