#1625·fiber

Design: Multi-tenant Hosted LSP support

Author: chenyukangCreated Aug 13, 2026Updated Sep 9, 2026
Labelsdesign

Summary

This proposal adds a multi-tenant Hosted LSP mode to Fiber for mobile and intermittently connected wallets.

A single Fiber process hosts multiple tenant-scoped data planes behind one public trampoline node. Each hosted tenant keeps its own invoice, payment, channel, and key namespace, while the public trampoline node owns the public P2P and gossip presence. When a tenant cannot immediately receive a payment, the LSP durably keeps the upstream TLC pending, activates or restores the tenant runtime, delivers the payment through the tenant's real private channel, and only then fulfills the upstream TLC.

From the payer's perspective this remains an ordinary trampoline payment. The payer does not need an LSP-specific payment protocol or knowledge of the receiver's tenant ID.

This is TLC buffering, not an internal LSP balance. Funds are not credited to a tenant until the private-channel state transition succeeds and the payment preimage can be propagated upstream.

Implementation status

Last reviewed on 2026-09-09 against PR #1623, branch chenyukang:lsp-support, head ab55edb. The remote-signer work from chenyukang/fiber#8 has been merged into this branch. These statuses describe the integrated prototype, not production readiness.

Phase Status Current branch coverage
1. Actor and state boundaries ✅ Implemented Public network commands/events are separated from FiberActorMessage; shared FiberActorCore/FiberActorState are used by a dedicated HostedTenantActor with no tenant NetworkActor.
2. Service and storage ✅ Implemented TenantRegistry, TenantSupervisor, cold/active runtime lifecycle, one-store namespaces, Biscuit tenant token issuance, and tenant-scoped standard RPC dispatch are present.
3. Private-channel local transport ✅ Implemented Restricted in-process peers, direct Fiber message delivery, private-channel restoration after hydration, and tenant open_channel restrictions are present.
4. Invoice routing and hosted delivery ✅ Implemented for the current hosted-process signing prototype Automatic trampoline hints/registration, durable delivery records, incoming-TLC execution keys, deadlines, quotas, transient/permanent failure handling, upstream settlement, and restart reconciliation are present. Buffered upstream MPP receipt remains out of scope.
5. Remote signer and SDK boundary Integrated polling prototype implemented Deterministic RootSigner registration, fiber-lsp-sdk/HostedSession, an independent SDK agent, persistent channel and watchtower signing requests, idempotent submission RPCs, tenant isolation, and remote-signer/watchtower E2Es are present. Production push delivery, signer-session fencing, crash-durable continuations, credential recovery/rotation, strict wallet policy, and complete external-signer outbound-payment coverage remain incomplete.

The integrated branch includes focused unit/integration tests and dedicated Bruno workflows for hosted payments, the independent SDK agent, external channel signing, and external-signer watchtower recovery.

Current verification:

  • Rustfmt, Clippy, typos, Cargo Shear, generated OpenRPC, and the WASM browser test passed at the reviewed PR #1623 head.
  • The dedicated lsp Bruno job passed at the reviewed PR head.
  • The independent fiber-lsp-sdk-agent Bruno job passed for registration, external funding, channel signing, agent restart/restore, and cooperative close.
  • External-signer watchtower Bruno jobs passed for force close, cold-tenant eviction, preimage claim, and stale-commitment revocation.
  • Focused Rust tests cover multi-tenant isolation, invoice registration, deadlines/quotas, error classification, delivery transitions, retry, settlement recovery, MPP outbound payment, and two-LSP payment routing.
  • Remote-signer tests cover RootSigner restore, channel-key isolation, typed signing, signer-store persistence, idempotent submission, cross-tenant rejection, cold status reads, and invalid-signature handling.
  • The core Check job passed at the reviewed PR head.
  • The full PR #1623 suite was not green at this review point: one non-LSP force-close-with-pending-tlcs-and-stop-watchtower E2E and the non-gossip benchmark failed, while the core Test job was still running.
  • Production signing policy, crash-durable ChannelActor continuations, signer-session fencing, credential recovery/rotation, and a complete external-signer outbound-payment E2E remain incomplete.

Motivation

Fiber is expected to run on Android and iOS. Mobile operating systems routinely suspend or terminate background processes, so a mobile node cannot reliably keep a P2P connection alive or sign every new channel commitment in real time.

Two existing mechanisms do not solve the complete problem:

  • A watchtower protects against invalid on-chain settlement, but it cannot accept and sign a new incoming TLC for an offline wallet.
  • A hold invoice delays preimage release after the final TLC has already been accepted, but an offline wallet may be unable to accept that TLC in the first place.

The LSP therefore needs to remain online, hold the payer's upstream TLC, and complete delivery to the hosted tenant after that tenant becomes ready.

Goals

  • Run one public Fiber node and multiple isolated hosted tenants in one process.
  • Keep one real private channel between each hosted tenant and the public trampoline node.
  • Avoid starting a full public NetworkActor, Tentacle service, gossip actor, or peer manager for every tenant.
  • Reuse the existing Fiber channel, payment, invoice, and TLC state machines.
  • Allow hosted tenants to pay through the public trampoline node without maintaining the public network graph.
  • Allow an offline tenant to receive a payment after its runtime and signer become ready.
  • Persist delivery state before dispatch so restart recovery is deterministic and idempotent.
  • Reuse standard tenant-facing RPCs under a tenant-scoped Biscuit token.
  • Keep public wire messages compatible with ordinary Fiber nodes.
  • Isolate all tenant state while keeping Public T, LSP metadata, and tenant data in one physical Fiber store.
  • Bound resource usage with global and per-tenant quotas.

Non-goals

The first version does not attempt to provide:

  • a production push/session protocol for the remote signer; the current integration uses JSON-RPC polling;
  • multi-process tenant migration, sharding, clustering, or high availability;
  • open LSP discovery or cross-provider interoperability;
  • a custodial account balance maintained by the LSP;
  • buffered MPP receipt and aggregation for one hosted invoice;
  • billing, pricing, or commercial settlement policy;
  • a production-ready mobile SDK transport and wallet policy;
  • a WASM-hosted public LSP service.

Terminology

Term Meaning
Payer P The external node that originates the payment.
Public T The one publicly reachable Fiber node operated by the LSP. It is also the trampoline hop.
Hosted Tenant U A tenant-scoped Fiber data plane hosted inside the LSP process.
Upstream TLC The TLC received by Public T from the payer side.
Downstream payment The payment created by Public T toward the hosted tenant.
Delivery The durable execution bridging one concrete upstream TLC to a downstream tenant payment.
Tenant ID An LSP-local authorization and storage namespace identifier. It is never put on the Fiber wire.
Tenant pubkey The protocol key used by the tenant endpoint for invoice signing, channel state, and in-process peer lookup. It is not a public gossip identity.
RootSigner The client-owned root signing identity. Its public key deterministically derives the tenant ID and authenticates initial tenant registration.
RootKey The RootSigner secret backup supplied by the client when creating or reopening the SDK signer. It is not stored in the ordinary signer store.
Channel signer A signer allocated by the RootSigner for one channel. It owns that channel's funding, TLC, commitment, and MuSig2 nonce secrets.
ChannelKeyId An SDK-local identifier used to reopen one channel signer. It is never used as an LSP authorization principal and does not need to be known by the Node.
Signing request A persisted typed request that pauses one ChannelActor transition until the external signer returns the required signature and optional next public material.

“Upstream” and “downstream” are always relative to Public T:

Payer P  -- upstream TLC -->  Public T / LSP  -- downstream payment -->  Hosted Tenant U

Design principles

  1. One public network identity. Only Public T participates in public P2P, gossip, announcement, and route construction.
  2. Real private channels. U-to-T channels keep the existing Fiber commitment, signing, TLC, and settlement semantics.
  3. Local transport, unchanged protocol. Co-located endpoints bypass Tentacle framing and sockets, but still exchange the existing Fiber messages.
  4. Type-level actor isolation. A hosted tenant mailbox cannot represent public network commands or events.
  5. Durable before side effects. Delivery state is persisted before a downstream payment is created.
  6. No implicit custody ledger. Success is defined by channel state and preimage propagation, not by an LSP database credit.
  7. Transparent payment path. An external payer sees an ordinary invoice with a trampoline route hint.
  8. Least-privilege RPC. The same RPC endpoint is shared, but a Biscuit token selects and restricts the tenant context.

Architecture

mermaid
flowchart TB
    P["External Fiber network<br/>payers and receivers"]

    subgraph CLIENTS["Independent client-side signer processes"]
        direction LR
        M1["SDK agent U1<br/>HostedSession + RootSigner"]
        M2["SDK agent U2<br/>HostedSession + RootSigner"]
        M3["SDK agent U3<br/>HostedSession + RootSigner"]
        S1["U1 RootKey backup<br/>and signer store"]
        S2["U2 RootKey backup<br/>and signer store"]
        S3["U3 RootKey backup<br/>and signer store"]
        M1 <--> S1
        M2 <--> S2
        M3 <--> S3
    end

    subgraph LSP["Multi-tenant Fiber LSP service"]
        direction TB

        API["Shared RPC gateway<br/>Biscuit authorization"]
        DM["LspPaymentDeliveryManager<br/>buffering, retry, recovery"]
        CTRL["TenantRegistry + TenantSupervisor<br/>register, hydrate, evict, readiness"]
        WT["Watchtower<br/>persistent external signing state"]

        subgraph RUNTIMES["Hosted tenant runtimes"]
            direction LR
            U1["HostedTenantActor U1<br/>tenant Fiber data plane"]
            U2["HostedTenantActor U2<br/>tenant Fiber data plane"]
            U3["HostedTenantActor U3<br/>tenant Fiber data plane"]
        end

        T["Public Trampoline T<br/>one public NetworkActor"]

        subgraph STORE["One physical Fiber Store"]
            direction LR
            PS["Public T and watchtower state"]
            LM["LSP metadata namespace"]
            TS["Hosted tenant namespaces<br/>channel state + signing requests"]
        end

        API --> CTRL
        API --> WT
        DM --> CTRL
        CTRL --> U1
        CTRL --> U2
        CTRL --> U3

        U1 <-->|"C1: real private channel<br/>in-process Fiber transport"| T
        U2 <-->|"C2: real private channel<br/>in-process Fiber transport"| T
        U3 <-->|"C3: real private channel<br/>in-process Fiber transport"| T

        T --> PS
        WT --> PS
        DM --> LM
        U1 --> TS
        U2 --> TS
        U3 --> TS
    end

    M1 -. "tenant RPC + channel/watchtower<br/>signing status and submission" .-> API
    M2 -. "tenant RPC + signer polling" .-> API
    M3 -. "tenant RPC + signer polling" .-> API
    T <-->|"P2P / Gossip"| P

The outer LSP box is the deployment and resource boundary. It is not itself another Fiber node. Public T is the only public node in that box.

Component responsibilities

Public Trampoline T

  • owns the public node key and public NetworkActor;
  • maintains P2P sessions, gossip, public graph, routing, and public liquidity;
  • terminates trampoline hops;
  • owns one real private channel with each tenant;
  • receives upstream TLCs and creates downstream payments;
  • registers active tenant actors as restricted in-process peers.

TenantRegistry

  • persists tenant records;
  • maps tenant ID to tenant pubkey, private channel, policy, and store namespace;
  • provides reverse lookup from tenant pubkey or channel to the owning tenant;
  • does not start actors or schedule payments.

TenantSupervisor

  • owns the runtime index from tenant ID to HostedTenantActor;
  • starts a cold tenant on demand;
  • restores persisted channel/payment state;
  • tracks readiness and channel availability;
  • evicts idle runtimes while retaining persistent state;
  • enforces the active-runtime limit.

HostedTenantActor

A hosted tenant is a lightweight Fiber data-plane actor. It reuses FiberActorCore and FiberActorState, including ChannelActor, PaymentActor, invoice state, payment sessions, and payment attempts.

It does not own a Tentacle service, public peer dialing, gossip synchronization, public graph, onion service, or public-node commands.

Its mailbox accepts only FiberActorMessage:

mermaid
flowchart LR
    subgraph PUBLIC["Public T runtime"]
        PC["PublicNetworkCommand<br/>PublicNetworkEvent"]
        NA["NetworkActor"]
        IDX["In-process peer index<br/>tenant_pubkey → FiberActorRef"]
        PC --> NA
        NA --> IDX
    end

    subgraph TENANT["Hosted tenant runtime"]
        FM["FiberActorMessage only<br/>Command / Event / Notification"]
        UA["HostedTenantActor<br/>FiberActorCore + FiberActorState"]
        CA["ChannelActor / PaymentActor"]
        FM --> UA --> CA
    end

    IDX <-->|"existing Fiber messages<br/>direct actor delivery"| UA
    B["Type boundary:<br/>public commands and events cannot enter<br/>the tenant mailbox"]
    PC -. "not representable" .-> B

LspPaymentDeliveryManager

  • recognizes a registered hosted invoice by payment hash;
  • validates amount, asset, expiry, channel binding, and quotas;
  • persists delivery state before dispatch;
  • asks TenantSupervisor to activate the tenant;
  • waits for tenant/channel/signer readiness;
  • creates or resumes the downstream payment through Public T;
  • classifies failures as retryable or permanent;
  • fulfills or fails the upstream TLC after the downstream result is known;
  • resumes non-final deliveries after restart.

It does not create tenant actors directly and does not mutate channel state directly.

Identity model

The remote-signer design separates four identities that have different owners and security roles:

Identity Purpose Owner
RootSigner public key Stable client signer identity and tenant registration authority Mobile client
Tenant ID Account, authorization, quota, and storage namespace identifier Deterministically derived by the LSP
Tenant pubkey Fiber protocol identity of Hosted Tenant U Hosted tenant runtime
Channel ID Identifier of one Fiber channel state machine Fiber protocol

A tenant ID is never selected by the client and is never placed on the Fiber wire. For authenticated registrations it is derived canonically from the compressed RootSigner public key:

tenant_id = lowercase_hex(
    ckb_blake2b_256(
        "fiber-hosted-lsp-tenant-id/v1" ||
        compressed_root_signer_pubkey
    )
)

This gives one RootSigner identity one stable hosted tenant. Restoring the same RootKey and signer identity restores the same tenant ID; generating a new RootSigner creates a different tenant.

The complete RootSigner public key is also persisted in the tenant record so the registry can verify the derivation invariant and support future authenticated recovery flows.

The tenant pubkey remains separate. It identifies Hosted Tenant U for its private U-to-T channel and the current hosted invoice/runtime model, but it is not a publicly advertised gossip identity.

A channel ID identifies protocol state, not authorization. Channel ownership is established by the authenticated tenant namespace and the registry's private-channel binding. The SDK may maintain a local mapping such as:

(LSP endpoint, tenant_id, channel_id) -> ChannelKeyId

The LSP does not need to know or validate ChannelKeyId.

RootSigner-authenticated registration

Only initial tenant registration requires a RootSigner signature. Later signer operations are authorized by the tenant Biscuit credential.

The canonical registration value is TenantRegistryPayload:

yaml
protocol: "fiber-hosted-lsp-tenant-registry/v1"
lsp_node_id: <Public T node ID>
root_signer_pubkey: <RootSigner public key>
nonce: <32 random bytes issued by the LSP>

The YAML representation documents the fields; the signature uses a fixed canonical binary encoding.

The nonce is generated by a cryptographically secure random number generator. It is not derived from time, tenant ID, public key, or client input. At most one nonce is current for a RootSigner: issuing a new nonce replaces the previous one, and successful registration consumes it.

The MVP payload intentionally has no expires_at. Unused nonce cleanup is an operational concern and is not part of signature verification.

mermaid
sequenceDiagram
    participant SDK as Mobile SDK / RootSigner
    participant API as LSP RPC gateway
    participant REG as TenantRegistry
    participant SUP as TenantSupervisor

    SDK->>API: lsp_get_tenant_registry_nonce(root_signer_pubkey)
    API->>REG: generate and persist replacement nonce
    REG-->>API: CSPRNG nonce
    API-->>SDK: lsp_node_id, root_signer_pubkey, nonce

    SDK->>SDK: build canonical TenantRegistryPayload
    SDK->>SDK: sign payload with RootSigner identity key
    SDK->>API: lsp_register_tenant(pubkey, nonce, signature)

    API->>API: rebuild payload and verify signature
    API->>API: derive tenant_id from RootSigner pubkey
    API->>REG: atomically create tenant and consume nonce
    REG->>SUP: provision or locate hosted tenant runtime
    API->>API: issue tenant-scoped Biscuit token
    API-->>SDK: tenant_id, tenant status, Biscuit token

The RootSigner does not need to sign ChannelOpenSignerMaterial. Possession of the tenant Biscuit token authorizes the caller to submit public channel material into that tenant's namespace. Subsequent signatures are cryptographically checked against the public material persisted in channel state.

This MVP model deliberately avoids a separate signer registry. Root-key rotation while preserving the same tenant, multiple authorized RootSigners, and recovery without the original RootKey require a future authorization and recovery design.

Remote signer and fiber-lsp-sdk

The remote signer is part of fiber-lsp-sdk; it is not embedded in the hosted Fiber process and is not a standalone Node-side signer service.

mermaid
flowchart TB
    subgraph CLIENT["Mobile wallet / SDK process"]
        APP["Wallet policy and user approval"]
        ROOT["RootKey + RootSigner<br/>tenant identity"]
        CS["Per-channel ChannelSigner<br/>funding, TLC, commitment keys<br/>MuSig2 secret nonces"]
        SS["SignerStore<br/>opaque signer safety state"]
        RPC["Hosted-LSP RPC adapter<br/>Biscuit authentication"]
        APP --> ROOT
        ROOT --> CS
        CS <--> SS
        APP --> RPC
        CS --> RPC
    end

    subgraph LSP["Hosted LSP proc