RLN bridge#

The delivery library (liblogosdelivery) does not implement RLN itself — it asks an external RLN module for every RLN operation. Its plugin is implementation-agnostic: it never names a registry or a membership, carries no configuration and never starts the backend. All of that lives here. This module answers those requests in-process: src/rln_bridge.cpp adds the registry id and rln identifier from the node’s preset, calls the co-loaded liblogos_rln_module through its generated typed client and feeds each reply back. Every request is also emitted as an rln*Request event for observability; rlnRespond exists to answer a request from outside, but on a bridge-enabled node the bridge answers first and a second response per reqId is rejected.

A get_membership_state reply crosses verbatim. The other three answer the result envelope, which the typed client decodes and the bridge re-emits field for field — the schema is still the RLN module’s and the delivery library’s, not modelled here. The only replies the bridge fabricates are failures: a transport problem is TRANSIENT, and a provider refusal — the module declining the call itself — is PERMANENT, because retrying a contract mismatch cannot fix it. If nothing answers a request at all, the library times it out itself and everything non-RLN keeps working.

Turning RLN on#

There is no RLN method to call. RLN comes from the network preset in the createNode config, because every value it needs — the registry, the epoch size, the application identifier — is a property of the deployment rather than of the caller. A client picks a network and gets whatever rate limiting that network runs.

Of the shipped presets only logos.test has RLN on; logos.dev and the preset-less "" have it off (see networks.md).

logos.test

Registry

logos:testnet:fd322d32a46ddf57eece9db8d68e9b722262d1f770760fca63b8cb9e7775b971 — the LEZ testnet registration program’s config account (base58 J3NXPWUspX2zqJU3tUbR6MHmm83aJc5AzvidEaBBKnat)

LEZ zone

LEZ v0.3.1, sequencer https://testnet.lez.logos.co/, channel 0101010101010101010101010101010101010101010101010101010101010101

Epoch size

600 s

Max epoch gap

the RLN module’s default (the preset sets none)

RLN identifier

this application’s, sha256("rln/logos-delivery/v0.0.1")

Validation

off — nodes attach proofs but do not validate incoming ones

liblogos_rln_module ≥ 0.12.0 with liblogos_lez_rln_module ≥ 5.1.0 map logos:testnet to that sequencer through their built-in network table, so a logos.test node needs no LEZ environment. Older lez modules link an older LEZ wallet that cannot sync this zone.

Sending needs an active RLN membership in that registry, registered out-of-band through the RLN module from a funded LEZ testnet payer. Receiving doesn’t. A node without a membership starts, subscribes, relays and can validate proofs. Only its sends fail.

Upgrading from 0.3.x. Before 0.4.0 logos.test named logos:testnet:841312e989c77e3f6f58a5d880a8e25b950b8b5ffba2f39748fa44622c20c893, a registry 0.4.0 no longer uses. A membership registered there does not carry over: under the new registry get_membership_state reports unknown, and sends fail until the node holds a membership in logos:testnet:fd322d32…7775b971. Register a new one from a funded payer on the v0.3.1 zone, with liblogos_rln_module ≥ 0.12.0 and liblogos_lez_rln_module ≥ 5.1.0 installed.

  • Installing the library’s RLN plugin is what makes it mount RLN, and it reads that at node creation, so createNode does it before handing the config to the library.

  • Bringing the backend up reaches the chain, so createNode runs that on its own thread and returns without waiting. Follow it with rlnState or the rlnStateChanged event: Disabled → Initializing → Ready | Failed.

  • This module starts liblogos_rln_module itself; the library no longer does. A bridge that cannot come up is not fatal: the rln*Request events plus rlnRespond remain, but nothing starts the backend on that path.

  • liblogos_rln_module is an optional_dependency, so the host neither loads it nor requires it: a node whose preset has RLN off runs without the RLN stack installed at all. A node on an RLN-enabled preset loads it — and its own dep, liblogos_lez_rln_module — before createNode, or bring-up ends in Failed.

  • Bring-up fires start from this module. If it fails, rlnState goes to Failed with the RLN module’s error.

  • At startup the library also checks the node’s membership with get_membership_state. The result doesn’t block startup: a node without an active or grace_period membership logs a notice and starts anyway.

Ready means the backend started and the bridge answers. It does not mean the RLN module’s valid-root window is warm — that is a background refresh the RLN module does not currently expose a probe for.

With rate limiting on (rate-limit-enabled), every send queries get_epoch_quota and is queued while remaining is 0.

Presets for a test or local deployment#

LOGOS_DELIVERY_RLN_PRESETS names a JSON file whose entries are merged over the built-in table, which is how a rig points a node at its own registry without any public API for it:

{
  "": {
    "enabled": true,
    "registry-id": "logos:testnet:<64 hex chars — the registration program's config account>",
    "epoch-size-sec": 120,
    "max-epoch-gap": 1
  }
}

rln-identifier is optional and defaults to this application’s scope, sha256("rln/logos-delivery/v0.0.1"). Name one only for a deployment that needs a scope of its own — every node that must validate another’s proofs has to use the same value, and two that disagree reject each other’s messages as invalid rather than reporting a misconfiguration. The 32 bytes are arbitrary to the protocol: the circuit path reduces them with hash_to_field_le (Keccak-256) however they were chosen.

Keys are preset names spelled exactly as the delivery library spells them — "", twn, logos.dev, logos.test, status.prod — and matched exactly. The empty name above is the preset-less config the library also accepts.

A variant spelling is an error, not a miss: logostest in this file, or in a node’s preset, fails rather than resolving to logos.test. The library accepts some of those variants for its own network config, so a node could otherwise come up on the right network with RLN silently off.

A file that cannot be read or parsed, an unknown preset name, or an enabled entry missing a required field all fail createNode rather than quietly producing a node without the rate limiting its deployment expects.

Running the e2e#

tests/e2e/run.sh boots a logosctl daemon over the four module bundles and asserts the bring-up chain; its header documents the env knobs. The daemon log — <run dir>/session/logs/daemon.log — carries the library’s log lines.

Time budgets#

The library gives each request a budget before synthesizing a TRANSIENT failure itself: 80 s for the registry reads (get_membership_state, generate_proof), 10 s for the rest. Each request op is one <name>AsyncResult call on the generated client carrying its own deadline — 70 s for the reads (just under the library’s budget, so the bridge’s answer lands first), 10 s for get_epoch_quota and validate_proof — and the reply arrives on the client’s completion callback. start and stop are synchronous typed-client calls with no library clock behind them; they carry 20 s, which is what the protocol default already gave them.