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>
|
||
|---|---|---|
| .. | ||
| asm-sequencer.service | ||
| README.md | ||
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
-
Create the service user.
sudo useradd -r -s /usr/sbin/nologin -d /nonexistent asm-seq -
Lay out the code. The service file assumes
/opt/deviantcontains 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).
-
Install the coordinator wallet keyfile. The service reads
.chipnet-keys-<COORD>.jsonfrom the repo root. For chipnet runs, copy.chipnet-keys-h.jsoninto/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 -
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 viascripts/chipnet-asm-genesis.mjs). -
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 -
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/claimafter restart re-fetches the on-chain tip (and if a prior chain is still unconfirmed, it will broadcast against the confirmed tip, which maytxn-mempool-conflictwith 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,TOKENenv. - Chipnet-only token right now (
chipnetprovider hardcoded inserver.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.