Skip to main content

Register an RLN membership on LEZ with a Delivery node

Start a Logos Delivery node on the DoS-protected logos.test network, register its RLN membership on the Logos Execution Zone, and send a rate-limited message.​

Version

This document is accurate for Testnet v0.3.0.

This procedure shows how to register an Rate-Limiting Nullifier (RLN) membership on the Logos Execution Zone, used to protect against denial-of-service attacks. Messages published on the LEZ must include a proof of membership. It is intended for node operators who want to join the DoS-protected network, and touches Logos Core, Logos Delivery, and LEZ in a single flow.

Prerequisites
  • A supported OS:
    • macOS (Apple Silicon)
    • Linux (x86-64 or aarch64)
  • logosctl installed.
    • Install it by running curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-logosctl.sh | sudo sh
  • curl and python3
  • Network access to github.com for the catalogue downloads, and to the testnet sequencer at https://testnet.lez.logos.co/
  • About 1 GB of free disk space. The modules take about 300 MB.
  • 250000000 units of native LEZ tokens for the node's payer account, from one of these sources:
    • A Logos Blockchain wallet you can bridge assets from. Testnet has no faucet.
    • A LEZ account you already hold on the testnet zone, with about 270000000 units so it can cover the transfer's fee reserve.

What to expect​

  • You can run a delivery_module node on logos.test with an active RLN membership registered on LEZ.
  • You can fund the node's payer account and watch the node register its membership by itself.
  • You can send a message that carries an RLN proof and confirm that it propagated and used up part of your epoch quota.

Step 1: Set up a working directory​

Run every command in this procedure in one shell, from one working directory.

  1. Create a working directory and change into it:

    mkdir -p ~/logos-rln && cd ~/logos-rln
  2. Set the session directory. Every logosctl command in this shell uses it to hold the daemon's state, its logs, and the installed modules:

    export LOGOSCTL_CONFIG_DIR=$PWD/session

Step 2: Start the daemon with a wallet home​

The node keeps its wallet, including its private key, in a wallet home directory. There is nothing to configure: the logos.test preset names the registry as logos:testnet:<config account>, and the LEZ RLN module maps testnet to the testnet sequencer from its built-in network table. The directory only fixes where the wallet lives.

  1. Create an empty wallet home:

    mkdir -p wallet-home
  2. Start the daemon. It passes its environment to the modules, which is how the wallet finds its home:

    LEE_WALLET_HOME_DIR=$PWD/wallet-home \
    NSSA_WALLET_HOME_DIR=$PWD/wallet-home \
    logosctl daemon start --detach

    Expected result: Daemon started (pid N) and the path of its log, session/logs/daemon.log. The command returns once the daemon is ready.

  3. Check the daemon's status:

    logosctl daemon status --json

    Expected result: "daemon":{"status":"running",...}, with capability_module, package_manager, and package_downloader loaded.

Step 3: Install and load the modules​

  1. Install the delivery module and the RLN module from the official catalogue. liblogos_rln_module pulls in liblogos_lez_rln_module:

    logosctl package install delivery_module liblogos_rln_module -y
  2. List the installed packages:

    logosctl package ls

    Expected result: delivery_module 0.3.3 or later, liblogos_rln_module 0.12.0 or later, and liblogos_lez_rln_module 5.1.0 or later, each with source user.

    • Older delivery modules point logos.test at a registry that is no longer used, and older LEZ RLN modules link a LEZ wallet that can't sync the LEZ v0.3.1 testnet zone.
  3. Load the delivery module. This also loads the RLN modules, which are its optional dependencies:

    logosctl module load delivery_module
    logosctl module ls --loaded

    Expected result: liblogos_lez_rln_module, liblogos_rln_module, and delivery_module are loaded.

Step 4: Create the delivery node​

  1. Capture the node's events in the background. The watcher only sees events emitted after it attaches, so start it first:

    logosctl --json watch delivery_module >> events.jsonl 2>&1 &
  2. Write the node config and create the node:

    cat > node.json <<'EOF'
    {"mode": "Core", "preset": "logos.test"}
    EOF
    logosctl --json call delivery_module createNode @$PWD/node.json
    FieldPurpose
    modeCore runs a full relay node.
    presetlogos.test brings the network settings (cluster 2, 8 auto-shards, built-in entry nodes) and its RLN settings: the registry on the LEZ testnet, the RLN identifier, and the epoch size.
    messagingOverridesOptional per-layer settings, for example {"tcp-port": 30303}. Keep extra keys inside it: a bare top-level key switches parsing to the legacy flat shape.

    Expected result: "success":true. Creating the node also starts the RLN module on the registry that the preset names. The LEZ RLN module writes wallet-home/wallet_config.json, pointing at the testnet sequencer, and wallet-home/network.json, containing {"network":"testnet","source":"table"}. From here on, the node registers itself once its payer account is funded.

  3. Poll the RLN state until it reports "state":"Ready":

    logosctl --json call delivery_module rlnState

    Expected result: the reply names the RLN scope the node uses:

    "registryId":"logos:testnet:<64 hex>", "rlnIdentifier":"<64 hex>", "epochSizeSec":<N>
  4. Keep both values for the RLN calls in later steps. Replace <registryId> and <rlnIdentifier> with the values from the reply. Write the identifier to a file so that logosctl can't mistake it for a number:

    REGISTRY=<registryId>
    printf '%s' '<rlnIdentifier>' > rln-id.arg

Step 5: Fund the node's payer account​

The node derives a payer account from its wallet. To register, it must hold the registry's price plus a fee reserve derived from the chain's base fee: 182800000 in total at the testnet's minimum fee, and more if fees rise. Sending 250000000 native units leaves headroom. The unused part of the reserve is refunded: a registration spends about 75000000, and the rest stays in the payer account.

  1. Poll the wallet status until state is ready:

    logosctl --json call liblogos_lez_rln_module wallet_status

    Expected result: {"status":"ok","result":{... "state":"ready", "network":"testnet", "payer":"<ACCOUNT>" ...}}. While the wallet syncs, state is pending.

  2. Convert the payer account ID to the Public/<base58> form that LEZ wallets and explorers use. Replace <ACCOUNT> with the 64-character hex value of payer, which is also the recipient a bridge deposit carries:

    ACCOUNT=<ACCOUNT>
    python3 -c '
    import sys
    A = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    h = sys.argv[1]
    n, s = int(h, 16), ""
    while n:
    n, r = divmod(n, 58)
    s = A[r] + s
    print("Public/" + "1" * ((len(h) - len(h.lstrip("0"))) // 2) + s)' $ACCOUNT
  3. Send 250000000 native units to the payer account:

    • To bridge from the Logos Blockchain, follow Bridge assets between the blockchain and a zone. A deposit is credited on LEZ only once it is final on the blockchain. On the current testnet that is about 10 hours after the deposit is included in a block, and the payer balance stays unchanged until then. The deposit's fee is paid on the blockchain side.
    • To transfer from an existing LEZ account, send to the Public/<base58> address. A transfer carries a fee reserve of about 19000000 on top of the amount, at the minimum fee for the wallet's standard 2M gas limit; all but the actual fee (about 100000) is refunded to the sender.
  4. Check the payer's balance. An empty argument file means the node's own payer:

    : > self.arg
    logosctl --json call liblogos_lez_rln_module get_native_balance @$PWD/self.arg

    Expected result: balance is at or above the amount you sent.

Step 6: Wait for the membership to register​

The node registers itself on the LEZ registry, typically one to three minutes after the funding lands.

  1. Poll the membership state until state is active:

    logosctl --json call liblogos_rln_module get_membership_state $REGISTRY @$PWD/rln-id.arg
    • While registering, the reply shows "state":"unknown" and "provisioning":{"step":"awaiting_funding"} or "step":"registering".
    • Once the registration is submitted, the reply shows "state":"pending" with a leaf_index, until the transaction is in a LEZ block.

    Expected result: "state":"active" (or "grace_period"), with a leaf_index and a membership_hash. "state":"failed" means registration failed; see Troubleshooting. If daemon.log says no membership within 300s, the module only stopped waiting: check that the sequencer is producing blocks with getLastBlockId, and keep polling.

Step 7: Start the node and send a message​

The node only starts with an active membership.

  1. Start the node and check that it reported nodeStarted:

    logosctl --json call delivery_module start
    until grep nodeStarted events.jsonl; do sleep 1; done
  2. Wait about 15 seconds for the node to join the Relay mesh, then subscribe to a content topic:

    sleep 15
    TOPIC=/my-app/1/hello/proto
    logosctl --json call delivery_module subscribe $TOPIC
  3. Note the epoch quota before sending. The str: prefix passes the timestamp as a string:

    logosctl --json call liblogos_rln_module get_epoch_quota $REGISTRY @$PWD/rln-id.arg str:$(date +%s)

    Expected result: {"epoch_index":N,"rate_limit":N,"remaining":N}.

  4. Send a message:

    printf '%s' "hello from my node" > payload.arg
    logosctl --json call delivery_module send $TOPIC @$PWD/payload.arg

    Expected result: "success":true, with a requestId as the value.

  5. Check that the node generated a proof and the message propagated:

    grep -iE 'rlnGenerateProofRequest|messagePropagated|messageError' events.jsonl

    Expected result: within about 30 seconds, a proof-generation request for the send (rlnGenerateProofRequest or dispatchRlnGenerateProofRequestEvent), then:

    {"event":"messagePropagated","data":{"arg0":"<requestId>","arg1":"<message hash>"}}
    • A messageError event with your requestId means the send failed; its data carries the reason.
  6. Run the get_epoch_quota call again.

    Expected result: in the same epoch_index, remaining is lower than before the send.

  7. Stop the daemon when you're finished:

    logosctl daemon stop
    • The wallet, membership, and installed packages stay in wallet-home/ and session/. To run the node again, start the daemon with the same environment as in Step 2, then module load delivery_module, start the event watcher, run createNode with the same node.json, and start once rlnState is Ready. The existing membership is picked up; nothing is registered or paid again.

Troubleshooting RLN membership​

A string argument arrives as a number​

logosctl call converts arguments that look numeric, which can catch a hex RLN identifier, a hash, or a payload. Write the value to a file with no trailing newline (printf '%s') and pass it as @/absolute/path; the file's contents are passed verbatim as a string. A str:<value> prefix also forces a string.

createNode fails with an unknown preset​

Spell the preset exactly logos.test. logostest is rejected, not treated as an alias.

rlnState stays Disabled​

The installed delivery_module is older than 0.3.0, where logos.test has RLN turned off. Check the version with logosctl package ls and upgrade with logosctl package install delivery_module -y.

rlnState reports Failed​

The reply carries the reason. A common one is a hand-written wallet_config.json, or a LEZ_RLN_SEQUENCER variable, pointing at a sequencer that isn't the testnet one, so the registry can't be found on-chain. Start with an empty wallet-home/ to let the module pick the sequencer. The module reads LEZ_RLN_SEQUENCER only when wallet-home/ has no wallet_config.json yet, and never rewrites an existing one.

Another is a delivery_module older than 0.3.3, whose logos.test preset names the old registry logos:testnet:841312e9…, which is no longer used. Check the version with logosctl package ls, and upgrade with logosctl package install delivery_module -y.

wallet_status reports failed, or daemon.log shows Unexpected variant tag​

The installed liblogos_lez_rln_module is older than 5.1.0 and links a LEZ v0.3.0 wallet, which can't read LEZ v0.3.1 blocks. The log line reads Sync failed: Parse error: Unexpected variant tag. Run logosctl catalog refresh, upgrade with logosctl package install liblogos_lez_rln_module -y, and start again with a fresh wallet-home/.

wallet_status stays pending with the network selected​

The node can't reach the testnet sequencer. Check it from the same machine, using the sequencer_addr from wallet-home/wallet_config.json (https://testnet.lez.logos.co/ with current modules); this request should answer with a block number:

curl -sS -X POST -H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","method":"getLastBlockId","params":[],"id":1}' \
https://testnet.lez.logos.co/

wallet_status stays pending with no network selected​

Nothing has named a registry yet. Run createNode with the logos.test preset as in Step 4. If it persists, the installed RLN modules are too old; see Step 3.

The wallet never becomes ready, or opens in the wrong place​

The wallet home variables were not set on the daemon start command; the modules only see the daemon's environment. Stop the daemon and start it again with them.

Registry calls fail with bound to network​

wallet-home/ was used for another network before. A wallet home bound to one network refuses another. Use a fresh wallet home per network.

provisioning.step stays at awaiting_funding​

The node waits indefinitely for its payer balance. Check get_native_balance and complete the funding transfer.

Registration fails with Incorrect fee​

The payer holds less than the price plus the fee reserve (182800000 in total at the minimum fee). Top it up to 250000000.

Registration never finishes and daemon.log contains outside registry bounds​

The registry rejected the requested rate limit. The log line names the allowed range.

get_epoch_quota returns rate_limit: 0​

The node has no usable membership yet; the quota is not exhausted. Wait for get_membership_state to report active.

send fails with no_usable_membership​

Registration has not finished. Wait for get_membership_state to report active.

Receivers drop the first message after registration​

A newly published tree root takes up to one root window to reach other nodes. Later sends are unaffected.

daemon start fails, or daemon status reports not_running​

Read session/daemon/startup.err and session/logs/daemon.log.

Every command fails with no daemon​

The command isn't using the daemon's session. Check that LOGOSCTL_CONFIG_DIR and TMPDIR match the ones the daemon was started with; the daemon's local socket lives in TMPDIR. Don't run the daemon under a private /tmp, such as systemd's PrivateTmp=yes.

package install does nothing and asks for confirmation​

Without a terminal, package install refuses to proceed unless given -y.

package install can't find liblogos_rln_module​

The session's catalogue index is stale. Run logosctl catalog refresh. logosctl catalog ls lists the enabled catalogues; the official one, logos-modules-official, must be enabled.

After a restart the membership reads unknown and sends fail​

A module process from the previous daemon outlived daemon stop and still holds the node's ports and the RLN keystore. Stop the daemon, look for processes left over from the previous daemon with ps -ef | grep logos, end them, and start again.