diff --git a/.github/workflows/build_docker.yaml b/.github/workflows/build_docker.yaml index 44fb9a8..a54de89 100644 --- a/.github/workflows/build_docker.yaml +++ b/.github/workflows/build_docker.yaml @@ -1,4 +1,4 @@ -# Build the three simplepool container images and push them to the GitHub +# Build the four simplepool container images and push them to the GitHub # Container Registry (ghcr.io) — the same registry coinshift-rs publishes to. # # This job runs only in the canonical LayerTwo-Labs repo (see the `if:` guard @@ -47,6 +47,8 @@ jobs: dockerfile: deploy/docker/Dockerfile.dashboard - image: simplepool-payout dockerfile: deploy/docker/Dockerfile.payout + - image: simplepool-slipstream + dockerfile: deploy/docker/Dockerfile.slipstream steps: - name: Checkout uses: actions/checkout@v4 @@ -88,6 +90,6 @@ jobs: github.event.pull_request.head.repo.full_name == github.repository }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - # Per-image GitHub Actions cache so the three legs don't collide. + # Per-image GitHub Actions cache so the legs don't collide. cache-from: type=gha,scope=${{ matrix.image }} cache-to: type=gha,mode=max,scope=${{ matrix.image }} diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml index b6fab09..e02c352 100644 --- a/.github/workflows/integration_tests.yaml +++ b/.github/workflows/integration_tests.yaml @@ -71,6 +71,13 @@ jobs: - name: Run coinbase-direct PPLNS end-to-end regtest test run: bash tests/test_pplns_coinbase_regtest.sh + # Slipstream: a tx submitted to the service is checked and broadcast to + # the node, reaches the enforcer's template through its mempool mirror, + # and is followed to confirmed. No proxy; the template is mined with + # generateblock. + - name: Run slipstream end-to-end regtest test + run: bash tests/test_slipstream_regtest.sh + - name: Upload logs if: failure() uses: actions/upload-artifact@v4 @@ -81,6 +88,8 @@ jobs: .regtest-e2e/logs/ .regtest-pplns/logs/ .regtest-cbwin/logs/ + .regtest/slipstream-e2e/logs/ + .regtest/slipstream-e2e/slipstream.log /tmp/simplepool-e2e.log /tmp/simplepool-e2e.conf /tmp/simplepool-int.log @@ -126,6 +135,14 @@ jobs: working-directory: payout run: npm test + - name: Install slipstream dependencies + working-directory: slipstream + run: npm ci || npm install + + - name: Run slipstream service tests + working-directory: slipstream + run: npm test + # The payout path the coinbase e2e skips: wallet-enabled enforcer + # thunder, a real deposit, and one payout tick broadcasting a Thunder # transaction (tests/test_payout_regtest.sh). Separate job purely for diff --git a/README.md b/README.md index 4ba0f82..76ea9e0 100644 --- a/README.md +++ b/README.md @@ -579,7 +579,7 @@ you can commit + push from here), use ### Docker An alternative to the bare-metal script: containerized builds of the -three services (stratum proxy, dashboard, payout worker) under +four services (stratum proxy, dashboard, payout worker, slipstream) under [`deploy/docker/`](deploy/docker/). One `docker compose up -d --build` gets the whole app stack running against a Thunder daemon and Bitcoin Core that live on the host (or wherever you point them). Shared bind @@ -592,6 +592,23 @@ Note: the drivechain infrastructure (`bitcoind`, Thunder, the those daemons have their own lifecycles and typically run bare-metal on the same host. +### Slipstream + +[`slipstream/`](slipstream/) is an optional service that takes a raw tx from +anyone and gets it into the pool's blocks, including txs the network will not +relay: BIP300/301 txs, or any other consensus-valid tx. It checks each one +against the pool's own bitcoind (`testmempoolaccept`) and the fee rule, then +broadcasts it there; the enforcer's template mempool mirrors that node's, so it +reaches the templates the proxy mines with no enforcer change. Non-standard txs +need the node to run `-acceptnonstdtxn` (which Core allows only off mainnet). +The fee rule is Slipstream's: the higher of a 1 sat/vB floor and the current +mineable rate. Every submission is kept, and each accepted tx is followed from +template to block. It also serves the pool's `info.json` for pool directories. +It is not set up by `install.sh` yet: the systemd unit and nginx vhost are +templates in [`deploy/`](deploy/), and [`slipstream/README.md`](slipstream/README.md) +walks through installing them. [`docs/simplepool.html`](docs/simplepool.html) +explains it end to end. + ## Config keys ``` @@ -748,6 +765,8 @@ scripts/ dashboard/ # Node/Express read-only stats UI payout/ # payout worker: Thunder rail (pps-classic, pplns-thunder) # and L1 rail via the enforcer wallet (pplns-btc) +slipstream/ # slipstream service: takes txs from anyone and gets them + # into the pool's blocks via its bitcoind; serves info.json docs/simplepool.html # single-file explainer: every mode, end to end ``` diff --git a/deploy/docker/Dockerfile.slipstream b/deploy/docker/Dockerfile.slipstream new file mode 100644 index 0000000..3a48757 --- /dev/null +++ b/deploy/docker/Dockerfile.slipstream @@ -0,0 +1,21 @@ +# ----------------------------------------------------------------------------- +# simplepool slipstream service — private tx submission (Node.js) +# ----------------------------------------------------------------------------- + +FROM node:20-bookworm-slim AS deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 make g++ ca-certificates \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /app +COPY slipstream/package.json slipstream/package-lock.json ./ +RUN npm ci --omit=dev + +# ----------------------------------------------------------------------------- +FROM node:20-bookworm-slim +RUN apt-get update && apt-get install -y --no-install-recommends tini \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY slipstream/ ./ +USER node +ENTRYPOINT ["/usr/bin/tini", "--", "node", "index.js"] diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index ae8285a..7c2fb47 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # simplepool — docker-compose orchestration # ----------------------------------------------------------------------------- -# Runs the three simplepool services against a Thunder daemon that lives on +# Runs the four simplepool services against a Thunder daemon that lives on # the host (or wherever THUNDER_RPC_URL points). Bitcoin Core is likewise # expected to be reachable from the simplepool container. # @@ -102,3 +102,45 @@ services: extra_hosts: - "host.docker.internal:host-gateway" stop_grace_period: 30s + + slipstream: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile.slipstream + image: simplepool-slipstream:latest + container_name: simplepool-slipstream + restart: unless-stopped + environment: + # The pool's bitcoind -- the node the enforcer mirrors its mempool + # from. Txs are checked and broadcast here. Run it with + # -acceptnonstdtxn to take non-standard txs (not allowed on mainnet). + BITCOIND_RPC_URL: ${BITCOIND_RPC_URL:-http://host.docker.internal:8332} + BITCOIND_RPC_USER: ${BITCOIND_RPC_USER:-} + BITCOIND_RPC_PASS: ${BITCOIND_RPC_PASS:-} + # The enforcer's block template server -- the proxy's bitcoind_url. + # Read only: the template is where the mineable rate comes from. + ENFORCER_GBT_URL: ${ENFORCER_GBT_URL:-http://host.docker.internal:8122} + SLIPSTREAM_DB_PATH: /data/slipstream.db + PROXY_DB_PATH: /data/shares.db + SLIPSTREAM_BIND: 0.0.0.0 + SLIPSTREAM_PORT: "8124" + SLIPSTREAM_TRUST_PROXY: ${SLIPSTREAM_TRUST_PROXY:-0} + SLIPSTREAM_MIN_FEE_RATE: ${SLIPSTREAM_MIN_FEE_RATE:-1} + POOL_NAME: ${POOL_NAME:-} + POOL_OPERATOR: ${POOL_OPERATOR:-} + POOL_CHAIN: ${POOL_CHAIN:-} + POOL_LOGO: ${POOL_LOGO:-} + POOL_CONTACT: ${POOL_CONTACT:-} + PUBLIC_STRATUM_URL: ${PUBLIC_STRATUM_URL:-} + PUBLIC_DASHBOARD_URL: ${PUBLIC_DASHBOARD_URL:-} + PUBLIC_SLIPSTREAM_URL: ${PUBLIC_SLIPSTREAM_URL:-} + ports: + # Loopback on the host: publish through nginx, which is also what + # SLIPSTREAM_TRUST_PROXY=1 assumes sets X-Forwarded-For. + - "127.0.0.1:${SLIPSTREAM_PORT:-8124}:8124" + volumes: + # Writes slipstream.db; reads shares.db. + - ../../data:/data + extra_hosts: + - "host.docker.internal:host-gateway" + stop_grace_period: 10s diff --git a/deploy/nginx/slipstream.conf b/deploy/nginx/slipstream.conf new file mode 100644 index 0000000..e13dd5b --- /dev/null +++ b/deploy/nginx/slipstream.conf @@ -0,0 +1,44 @@ +# slipstream.example — reverse proxy for the simplepool slipstream service. +# +# The service is bound to 127.0.0.1:8124 (see +# deploy/systemd/simplepool-slipstream.service) and keys its per-client +# submission limit on the X-Forwarded-For hop set here. Replace +# slipstream.example with your hostname, then: +# +# sudo cp deploy/nginx/slipstream.conf /etc/nginx/sites-available/ +# sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/ +# sudo nginx -t && sudo systemctl reload nginx +# sudo certbot --nginx -d # TLS, edits this file in place + +server { + listen 80; + listen [::]:80; + server_name slipstream.example; + + # Reuses the dashboard's zone (deploy/nginx/pool-ratelimit.conf) for + # reads. Submissions have their own, stricter limit inside the service. + limit_req zone=pool_dash burst=20 nodelay; + + # A raw tx arrives as hex: up to 1MB of tx is 2MB of body. + client_max_body_size 3m; + + access_log /var/log/nginx/slipstream.example.access.log; + error_log /var/log/nginx/slipstream.example.error.log warn; + + location / { + proxy_pass http://127.0.0.1:8124; + proxy_http_version 1.1; + proxy_set_header Host $host; + # Overwritten, not appended: the service trusts this hop, so the + # client must not be able to put its own in front of it. + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 30s; + proxy_connect_timeout 5s; + } + + location = /healthz { + proxy_pass http://127.0.0.1:8124/healthz; + access_log off; + } +} diff --git a/deploy/systemd/simplepool-slipstream.service b/deploy/systemd/simplepool-slipstream.service new file mode 100644 index 0000000..38e5124 --- /dev/null +++ b/deploy/systemd/simplepool-slipstream.service @@ -0,0 +1,55 @@ +[Unit] +Description=simplepool slipstream (tx submission) +Documentation=https://github.com/LayerTwo-Labs/simplepool/blob/main/slipstream/README.md +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=@USER@ +Group=@USER@ +WorkingDirectory=@ROOT@/slipstream +# The pool's bitcoind: the node the enforcer mirrors its mempool from. Txs +# are checked (testmempoolaccept) and broadcast (sendrawtransaction) here. +# For non-standard txs it must run -acceptnonstdtxn, which Core allows only +# off mainnet. Credentials: user/pass, or BITCOIND_RPC_COOKIE_FILE instead. +Environment=BITCOIND_RPC_URL=http://127.0.0.1:8332 +# Environment=BITCOIND_RPC_USER= +# Environment=BITCOIND_RPC_PASS= +# Environment=BITCOIND_RPC_COOKIE_FILE=/var/lib/bitcoind/.cookie +# The enforcer's block template server -- the same URL as bitcoind_url in +# proxy.conf. Read only: the template it serves is where the mineable rate +# comes from and how a tx is seen to be in play. +Environment=ENFORCER_GBT_URL=http://127.0.0.1:8122 +Environment=SLIPSTREAM_DB_PATH=@ROOT@/data/slipstream.db +Environment=PROXY_DB_PATH=@ROOT@/data/shares.db +# Loopback only; nginx publishes it (deploy/nginx/slipstream.conf), and +# sets the X-Forwarded-For hop the rate limit is keyed on. +Environment=SLIPSTREAM_BIND=127.0.0.1 +Environment=SLIPSTREAM_PORT=8124 +Environment=SLIPSTREAM_TRUST_PROXY=1 +Environment=SLIPSTREAM_MIN_FEE_RATE=1 +# info.json presentation. Facts about the pool (mode, fee, coinbase tag, +# addresses) are read from pool_meta and are NOT set here. +# Environment=PUBLIC_SLIPSTREAM_URL=https://slipstream.example +# Environment=PUBLIC_STRATUM_URL=stratum+tcp://stratum.example:3334 +# Environment=PUBLIC_DASHBOARD_URL=https://pool.example +# Environment=POOL_NAME= +# Environment=POOL_OPERATOR= +# Environment=POOL_CHAIN= +# Environment=POOL_LOGO= +# Environment=POOL_CONTACT= +ExecStart=/usr/bin/node @ROOT@/slipstream/index.js +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=full +ProtectHome=read-only +ReadWritePaths=@ROOT@/data + +[Install] +WantedBy=multi-user.target diff --git a/docs/sequence-diagrams.py b/docs/sequence-diagrams.py index 943deb2..bc4a3a6 100755 --- a/docs/sequence-diagrams.py +++ b/docs/sequence-diagrams.py @@ -274,6 +274,35 @@ def build(title, desc, actors, steps, accent="pplns", min_width=0): +# ---------------------------------------------------------- slipstream ----- +DIAGRAMS['slipstream'] = build( + 'slipstream: a tx from anyone, into the pool\'s blocks', + 'A submitter posts a raw tx. The slipstream service checks it against the ' + 'pool\'s bitcoind and the fee rule without broadcasting, then broadcasts it. ' + 'The enforcer mirrors that node\'s mempool, so the tx reaches the template ' + 'the pool mines; the service follows it to a block.', + [('sub', 'Submitter', 'any wallet'), + ('slip', 'slipstream', ':8124'), + ('node', 'bitcoind', '-acceptnonstdtxn'), + ('enf', 'enforcer', 'template server'), + POOL], + [('msg', 'sub', 'slip', 'POST /api/tx '), + ('msg', 'slip', 'node', 'testmempoolaccept'), + ('msg', 'node', 'slip', 'allowed? fee, vsize', 'dashed'), + ('self', 'slip', 'fee rule: max(floor, mineable)'), + ('note', 'Checked BEFORE it is sent: nothing can be taken back out of a mempool, so refusing afterwards would be too late.', 'warn'), + ('msg', 'slip', 'node', 'sendrawtransaction'), + ('msg', 'node', 'enf', 'mempool mirror (ZMQ)'), + ('note', 'From here it is an ordinary mempool tx: relayed to peers, and minable by any pool whose node took it.'), + ('msg', 'pool', 'enf', 'getblocktemplate'), + ('msg', 'enf', 'pool', 'a template carrying the tx', 'dashed'), + ('msg', 'slip', 'enf', 'poll: in the template?'), + ('msg', 'slip', 'node', 'poll: mined? how deep?'), + ('note', 'Every submission is kept, refusals included. Each accepted tx is followed to confirmed, or to dropped with the node\'s own reason.', 'win'), + ], min_width=600) + + + # ---- splicing ------------------------------------------------------------- # # Each figure sits between HTML comment markers so a regeneration replaces diff --git a/docs/simplepool.html b/docs/simplepool.html index fb29ed2..0dd9cae 100644 --- a/docs/simplepool.html +++ b/docs/simplepool.html @@ -308,6 +308,8 @@

simplepool

  • Auditing every number
  • The data model
  • Connect a miner
  • +
  • The dashboard
  • +
  • Slipstream
  • Configuration
  • Running one
  • What it can't do
  • @@ -710,6 +712,20 @@

    The stack

    pool:tip, pool:credits). SQLite stays authoritative; the publish is fire-and-forget and a Redis outage cannot cost you a share.

    + +

    + The proxy takes templates from the enforcer's template server, never from + bitcoind directly: only the enforcer's carry the BIP300/301 commitments a + sidechain needs. It long-polls (BIP22), so a new tip reaches miners as soon + as the enforcer has it rather than on the next poll. +

    + +

    + Optionally, the slipstream service sits beside the + proxy and takes transactions from anyone. It hands them to the pool's + bitcoind; the enforcer's template mempool mirrors that node's, so they reach + the pool's templates without any change to the enforcer. +

    @@ -2067,6 +2083,27 @@

    The data model

    it is stated here rather than enforced by a trigger that would hide it.

    + +

    The slipstream service keeps its own

    +

    + data/slipstream.db, written only by the + slipstream service, which opens + shares.db read-only for pool_meta and + blocks_found and never writes to it. +

    +
    + + + + + + + + + + +
    TableWhat it holds
    slipstream_submissionsevery POST exactly as it arrived, accepted or refused, with the reason — the record of what was asked of the pool
    slipstream_txsone row per accepted tx: fee and the rate it was held to, where it stands now, when it was first and last in a template, the block that mined it and whether that block was the pool's, and the raw tx, so it can be sent again
    slipstream_eventsevery status change, append-only, so a tx's history can be read back rather than inferred
    +
    @@ -2190,7 +2227,181 @@

    What will not disconnect you

    - + +
    +

    The dashboard

    +

    + Everything the proxy writes down, readable by anyone. The dashboard reads + shares.db and never keeps a second copy of the pool's config: + the mode, the fee, the rate and the ports it shows are the ones the proxy + wrote into pool_meta, so the page cannot disagree with the + process that did the work. +

    + +

    Public pages

    +
    + + + + + + + + + + +
    PathWhat it shows
    /hashrate, the leaderboard (per worker and per address), recent blocks, how to connect with each port's difficulty policy, and a card explaining every number on the page for this pool's mode
    /worker/:nameone worker: shares, hashrate, credits and payouts
    /blocksevery block the pool found, paginated, with its status and what it paid
    /templatesthe work being handed out: height, block value, fees, the sidechain commitments it carries, and the template history
    /slipstreamonly when slipstream runs: its fees, how to submit, and each recent submission with where it stands
    /healththe hard-failure checks — ledger arithmetic, rate, duplicates — with a 503 while any is failing, so an uptime monitor can watch it
    +
    + +

    JSON, for monitors and other tools

    +
    + + + + + + + + +
    PathWhat it returns
    /api/statuseverything at once: the pool's identity and totals, the node, the health checks, and which commit of each component is running. Always 200 — read health.ok
    /api/overview, /api/leaderboard, /api/worker/:name, /api/blocks, /api/templates, /api/nodethe data behind each page
    /api/versionsbuild provenance of simplepool, the enforcer, Thunder and bitcoind
    /healthzliveness only
    +
    + +

    Admin

    +

    + /admin, behind basic auth, is where the operator acts: balances + and what is owed, per-worker audits, deposits into the Thunder reserve, + payouts and a "pay now" trigger, and a tools page for the stuck cases. Every + write action is CSRF-gated, and every broadcast attempt is logged in + tx_attempts, successful or not. +

    +
    + + +
    +

    Slipstream

    +

    + An optional service that takes a raw transaction from anyone and gets it + into the pool's blocks — including the ones the network will not relay: + BIP300/301 deposits, withdrawal bundles and BMM requests, or anything else + that is consensus-valid. It borrows the fee rule of Marathon's Slipstream. +

    + +

    How a transaction gets in

    +

    + The service checks the transaction against the pool's own bitcoind with + testmempoolaccept, which reports the fee and size and + broadcasts nothing, applies the fee rule, and only then sends it with + sendrawtransaction. The enforcer's template mempool mirrors + that node's mempool, so the transaction reaches the template the proxy is + mining with no enforcer change — and with the enforcer's own BIP300 rules + still applied on the way in. +

    + + +
    +
    + + slipstream: a tx from anyone, into the pool's blocks + A submitter posts a raw tx. The slipstream service checks it against the pool's bitcoind and the fee rule without broadcasting, then broadcasts it. The enforcer mirrors that node's mempool, so the tx reaches the template the pool mines; the service follows it to a block. + + + + + + + Submitterany walletslipstream:8124bitcoind-acceptnonstdtxnenforcertemplate serversimplepool:3334 + + POST /api/tx <raw hex>testmempoolacceptallowed? fee, vsizefee rule: max(floor, mineable)Checked BEFORE it is sent: nothing can be taken back out of a mempool, so refusing afterwards would be too late.sendrawtransactionmempool mirror (ZMQ)From here it is an ordinary mempool tx: relayed to peers, and minable by any pool whose node took it.getblocktemplatea template carrying the txpoll: in the template?poll: mined? how deep?Every submission is kept, refusals included. Each accepted tx is followed to confirmed, or to dropped with the node's own reason. + +
    +
    + + +

    The fee rule

    +

    + A transaction must pay the higher of the minimum submission rate + and the current mineable rate. The minimum is + SLIPSTREAM_MIN_FEE_RATE, 1 sat/vB by default. The mineable + rate is read off the template being mined: while it has room, anything over + the minimum gets in, so the two are equal; once it is full, it is the rate + of the cheapest transaction it carries. The rule is checked once, at + submission; after that a transaction competes by fee rate like any other. +

    +
    + Why the order matters +

    + Nothing can be taken back out of a node's mempool. A transaction that + paid too little and was refused after it was sent would be + relayed and mined anyway, for less than was asked. So the rule runs + between the check and the broadcast, never after. +

    +
    + +

    Following it to a block

    +
    + + + + + + + + + +
    StatusMeans
    pendingin the node's mempool, not in the latest template
    in_templatein the latest template: the pool's miners are working on it now
    minedin a block, recorded as the pool's or another pool's from blocks_found
    confirmedSLIPSTREAM_CONFIRMATIONS deep, 6 by default
    droppedleft the mempool unmined, and the node refused it when it was sent again; the reason is the node's own, e.g. a replacement or a spent input
    +
    +

    + A transaction whose block is orphaned goes back to pending when + the node restores it to its mempool, which it does by itself. Every + submission is kept, refusals included, and every status change is logged. +

    + +

    info.json

    +

    + The service also answers GET /info.json, the listing a pool + directory reads. Its facts — mode (the exact + pool_mode), fee_bps, coinbase_tag, + operator_address, pool_btc_address — come from + pool_meta, never from the service's config. Name, chain, logo, + contact and the public URLs, slipstream_url among them, come + from its environment. +

    + +

    What it needs

    +
      +
    • + A node that takes non-standard transactions: + acceptnonstdtxn=1. Core refuses that setting on a chain that + reports itself as mainnet — drivechain-patched builds included — so a + mainnet-fork chain needs a Core patch that lifts the check before it can + take them. BIP300 deposits are standard on a drivechain-patched node + already and need no setting at all. +
    • +
    • + Acceptance of relay. An accepted transaction is broadcast + like any other, so another pool may mine it first. That is the price of + needing nothing from the enforcer, and the status table records whose + block it was. +
    • +
    +
    + +

    Configuration

    @@ -2244,7 +2455,7 @@

    Configuration

    bitcoind_user / bitcoind_pass— Optional — omit both for an unauthenticated backend and the call goes out with no auth header. Cookie auth is not supported. bitcoind_poll_interval_ms30000 - Template refresh. On a drivechain pool this is also the worst-case delay before a sidechain's BMM request can reach a job — lower it to 5000–10000 if sidechains need to merge-mine reliably. + Template refresh when the backend does not long-poll. The enforcer does (BIP22), and then the proxy wakes on each new tip instead and this only paces retries. Against a backend that does not, it is also the worst-case delay before a sidechain's BMM request can reach a job — lower it to 5000–10000 if sidechains need to merge-mine reliably. coinbase_tag/simplepool/ Short string baked into the coinbase scriptSig. db_path./data/shares.db @@ -2266,11 +2477,16 @@

    Configuration

    PAYOUT_SETTLE_INTERVAL_MS, PAYOUT_MIN_SATS, THUNDER_FROM_ADDRESS and friends. The Thunder reserve address is deliberately not a proxy key: the coinbase never touches Thunder, so - only the dashboard and the payout worker have any business knowing it. + only the dashboard and the payout worker have any business knowing it. The + slipstream service is configured the same way — + BITCOIND_RPC_URL, ENFORCER_GBT_URL, + SLIPSTREAM_MIN_FEE_RATE and the POOL_* / + PUBLIC_* presentation fields; slipstream/lib/config.js + has the full list.

    - +

    Running one

    @@ -2303,13 +2519,22 @@

    What runs

    - - - + + + +
    UnitModeJob
    simplepool.serviceboththe stratum proxy — the only thing that is strictly required
    simplepool-dashboard.servicebothread-only public stats on :8081, plus /admin behind basic auth
    simplepool-payout.servicepps-classicthe daily Thunder payout batch
    simplepool.serviceallthe stratum proxy — the only thing that is strictly required
    simplepool-dashboard.serviceallread-only public stats on :8081, plus /admin behind basic auth
    simplepool-payout.servicepps-classic, pplns-thunder, pplns-btcthe daily payout batch, over Thunder or on L1 through the enforcer's wallet
    simplepool-slipstream.serviceany, optionalthe slipstream service on :8124, published through nginx
    +

    + Slipstream is not set up by the installer yet. Its unit and nginx vhost are + templates in the repository — deploy/systemd/simplepool-slipstream.service + and deploy/nginx/slipstream.conf — and + slipstream/README.md walks through installing them. The + docker-compose stack runs it as a fourth container. +

    +

    Which commit is running

    The build commit is compiled into the binary, so simplepool @@ -2322,7 +2547,7 @@

    Which commit is running

    - +

    What it can't do

    @@ -2376,6 +2601,14 @@

    What it can't do

    dashboard says so plainly, rather than letting either half be found out when an order is cancelled. +
  • + Slipstream does not keep transactions private. It + broadcasts through the pool's bitcoind, so another pool can mine a + submitted transaction first, and it cannot withdraw one once sent. On a + chain that reports itself as mainnet the node cannot run + acceptnonstdtxn without a Core patch, so there it takes only + standard transactions — which includes BIP300 deposits. +
  • The submit ceiling bounds a flood, it does not end one. A mismatched connection is refused cheaply instead of validated, but it is @@ -2392,8 +2625,8 @@

    What it can't do