deviant/scripts/asm-sequencer/deploy
Local Dev 07fd344299 feat(asm-sequencer): systemd + user-funded /tip+/submit + wallet client
Three-part completion of the tier-2 arc from 0278:

1. systemd — scripts/asm-sequencer/deploy/{asm-sequencer.service,README.md}
   Sandboxed unit (NoNewPrivileges, ProtectSystem=strict, PrivateTmp,
   MemoryDenyWriteExecute), EnvironmentFile at /etc/asm-sequencer/env,
   SIGINT graceful shutdown, auto-restart, nginx reverse-proxy snippet.

2. User-funded endpoints — server.mjs adds:
   GET  /tip      current reservoir + token metadata for local tx build
   POST /submit   body {signedTxHex, tipTxid, tipVout, amountUnits}
                  under mutex: verify tip, provider.sendRawTransaction,
                  advance local chain. 409 stale with currentTip on race.

   Chipnet proof via scripts/chipnet-asm-sequencer-userfunded-test.mjs:
     5/5 accepted, 15 total /submit attempts (mean 3.0), 6.6 s wall time,
     daemon chain depth 0 → 5. Users signed their own BCH inputs locally;
     daemon received signed hex only, never saw a user key.
     Txids: 32ba4055… → 10945cf9… → 71ef9a7e… → ed8fedaa… → 05e7f66d…

   Funded A/C/E/F/G from H via new scripts/chipnet-fund-from-h.mjs
   (one tx dc725fa5f3252b40…, 5×150k sats outputs, 32.6M-sat change
   back to H).

3. Wallet-side client — sequencerRelay.ts in both:
   - shells/app/src/asm-core/
   - design/potidaea/src/

   Same API: getSequencerEndpoint/setSequencerEndpoint (localStorage
   key asm-sequencer.endpoint), sequencerReachable, sequencerClaim,
   claimViaRelayOrFallback. Probe-first-then-commit dispatch — the
   relay and local path both advance the SAME reservoir UTXO so parallel
   execution would create a self-double-spend risk; the probe cost is
   worth paying for correctness. Default disabled; opts in via LS key.

Snapshot 0279 + wiki/asm/zero-contention-protocol.md updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-02 00:21:51 +02:00
..
asm-sequencer.service feat(asm-sequencer): systemd + user-funded /tip+/submit + wallet client 2026-10-02 00:21:51 +02:00
README.md feat(asm-sequencer): systemd + user-funded /tip+/submit + wallet client 2026-10-02 00:21:51 +02:00

ASM sequencer — deploying under systemd

The sequencer service is a long-running HTTP daemon that holds the chain-tip for one backed asset category and serializes /claim requests into an ordered chain. It is a trust-minimised liveness service — the covenant enforces the swap math; the daemon can only order, delay, or refuse.

See ../README.md and wiki/asm/zero-contention-protocol.md for the full protocol context.

One-time host setup

  1. Create the service user.

    sudo useradd -r -s /usr/sbin/nologin -d /nonexistent asm-seq
    
  2. Lay out the code. The service file assumes /opt/deviant contains the repo. Easiest:

    sudo mkdir -p /opt/deviant
    sudo chown -R asm-seq:asm-seq /opt/deviant
    sudo -u asm-seq git clone <this-repo-url> /opt/deviant
    sudo -u asm-seq bash -c 'cd /opt/deviant && npm ci --omit=dev'
    

    Node 20+ required (import.meta.dirname, native fetch).

  3. Install the coordinator wallet keyfile. The service reads .chipnet-keys-<COORD>.json from the repo root. For chipnet runs, copy .chipnet-keys-h.json into /opt/deviant/. Keep it mode 600 and owned by asm-seq.

    sudo cp /path/to/.chipnet-keys-h.json /opt/deviant/
    sudo chown asm-seq:asm-seq /opt/deviant/.chipnet-keys-h.json
    sudo chmod 600 /opt/deviant/.chipnet-keys-h.json
    
  4. Install the token state file. The service reads ChipnetTests/.chipnet-<TOKEN>-token.json. Make sure that file ships with the deploy (it does today — ChipnetTests/ is dev state locally, but the specific token-state JSONs are small and can be copied to the host, or regenerated via scripts/chipnet-asm-genesis.mjs).

  5. Create the environment file.

    sudo install -d -m 750 -o root -g asm-seq /etc/asm-sequencer
    sudo tee /etc/asm-sequencer/env > /dev/null <<'EOF'
    PORT=8710
    COORD=h
    TOKEN=asm2
    MAX_CHAIN_DEPTH=200
    EOF
    sudo chmod 640 /etc/asm-sequencer/env
    sudo chown root:asm-seq /etc/asm-sequencer/env
    
  6. Install the unit.

    sudo install -m 644 /opt/deviant/scripts/asm-sequencer/deploy/asm-sequencer.service /etc/systemd/system/
    sudo systemctl daemon-reload
    sudo systemctl enable --now asm-sequencer
    sudo systemctl status asm-sequencer
    

Verifying

curl -s http://127.0.0.1:8710/health | jq

Expected: {ok: true, category, ticker, reservoir: {...}, coordinator: {...}, chainDepth, totalServed}.

Logs:

journalctl -u asm-sequencer -f

Reverse proxy

The daemon binds to 127.0.0.1:$PORT only. Expose it on a public host via nginx/caddy with TLS + rate limiting. Example nginx fragment:

location /asm-sequencer/ {
    proxy_pass http://127.0.0.1:8710/;
    proxy_read_timeout 30s;
    limit_req zone=asm_seq_zone burst=20 nodelay;  # define a 10 r/s zone in http {}
}

Updates & rollback

sudo -u asm-seq git -C /opt/deviant pull
sudo systemctl restart asm-sequencer

Rollback: git checkout <sha> on the deploy clone then restart.

Caveats

  • In-memory state. Restart loses chainDepth, totalServed, lastError. The next /claim after restart re-fetches the on-chain tip (and if a prior chain is still unconfirmed, it will broadcast against the confirmed tip, which may txn-mempool-conflict with the still-pending chain — wait for a block before restarting under load).
  • One category per service instance. Serve another asset by creating a second unit with its own PORT, COORD, TOKEN env.
  • Chipnet-only token right now (chipnet provider hardcoded in server.mjs). Mainnet is a one-line swap once the mainnet token state file exists.
  • Coordinator-funded trust model. The daemon holds the BCH funding. Users receive the FT; they do not need to sign anything. A user-funded variant (SIGHASH_ANYONECANPAY, /tip + /submit) removes this — see the server source for the newer endpoints.