Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
bb8353c
Update explorer reference to zkcoins.space
TaprootFreak May 16, 2026
a34fb27
docs: add Lightning ↔ zkCoins atomic swap design
TaprootFreak May 17, 2026
f74b619
docs: add BitVM2 bridge / trustless mint design
TaprootFreak May 17, 2026
30fd9ce
docs(BITVM_BRIDGE): cover Glock, Mosaic, BitVM3-RSA withdrawal
TaprootFreak May 17, 2026
1fef62a
docs: add Bridge MVP engineering spec
TaprootFreak May 17, 2026
8dd04e6
docs: set N=100 as production federation target
TaprootFreak May 17, 2026
0eb6ae3
docs: consistency audit pass
TaprootFreak May 17, 2026
556b2f6
docs: consistency audit round 2
TaprootFreak May 17, 2026
43d78ed
docs: consistency audit round 3
TaprootFreak May 17, 2026
65c9945
fix(balance): return 200 with balance:0 for unobserved addresses (#21)
TaprootFreak May 17, 2026
a2b93dd
docs(balance): document 200/422/404 contract; test username on unobse…
TaprootFreak May 18, 2026
363dbbb
fix(balance): return 422 for missing address param, matching other ma…
TaprootFreak May 18, 2026
35d4eb9
feat(info): expose Cargo feature gates via capabilities (closes #29)
TaprootFreak May 18, 2026
ead05ee
ci: slim CI to lint+build, move tests+coverage to enforced pre-push h…
TaprootFreak May 18, 2026
6198690
feat(info): report username_domain so the client can render <x>@<doma…
TaprootFreak May 18, 2026
937925a
feat: Plonky2 / Poseidon proof-system migration (#17)
TaprootFreak May 18, 2026
75051c5
ci: fix clippy crate rename + scope pre-push hook to actual file chan…
TaprootFreak May 18, 2026
d5cb60c
fix(faucet+runtime): unblock DEV bootstrap after Plonky2 migration (#36)
TaprootFreak May 19, 2026
446f502
feat(prepush): route verification to a remote host via ZKCOINS_PREPUS…
TaprootFreak May 19, 2026
4994b5c
test(account_server): cover send_coins MMR-sibling rejection path (#39)
TaprootFreak May 19, 2026
cf648d4
ci: add gated self-hosted runner jobs for tests + coverage (issue #40…
TaprootFreak May 19, 2026
0969851
hooks: shrink pre-push to fmt + clippy + check (issue #40, part 2/2) …
TaprootFreak May 19, 2026
48ace91
ci: activate server-tests + coverage on the self-hosted runner (#43)
TaprootFreak May 19, 2026
ff37593
ci: prepend ~/.cargo/bin so self-hosted jobs use rustup-managed Rust …
TaprootFreak May 19, 2026
3b9d82d
ci: skip CI on draft PRs, fire on ready_for_review (#46)
TaprootFreak May 19, 2026
7abe286
docs(ci-runner): rewrite README to reflect actual deployment (#44)
TaprootFreak May 19, 2026
2d129b4
ci: gate heavy M3 Ultra jobs behind `ci:full` PR label (#48)
TaprootFreak May 19, 2026
fb4e668
docs: post-Plonky2 consistency cleanup + remove internal host refs (#49)
TaprootFreak May 19, 2026
3a87950
docs(ci-runner): genericize host references for the public repo (#47)
TaprootFreak May 19, 2026
5c5b963
ci: harden deploy workflows — concurrency guards + PRD smoke test (#51)
TaprootFreak May 19, 2026
7f1893b
test(server_runtime): regression guard for Goldilocks-safe minting ba…
TaprootFreak May 20, 2026
3b67aad
docs: post-Plonky2 consistency cleanup (round 2) (#53)
TaprootFreak May 20, 2026
7cc07a6
feat(postgres): full state-layer migration (PR-A1 + PR-A2 + PR-A3)
TaprootFreak May 21, 2026
613e4d1
test(shared): Schnorr-signature negative paths for Commitment (#58)
TaprootFreak May 21, 2026
35a8244
test(publisher): wiremock-backed coverage for inscription + Esplora w…
TaprootFreak May 21, 2026
b48100d
ci: trigger on every pull request regardless of target branch (#61)
TaprootFreak May 21, 2026
c5f0f0f
feat(server): /health/ready endpoint (DB + Esplora readiness probe) (…
TaprootFreak May 21, 2026
fc43dd2
docs: trust model — hosted vs self-hosted (#71)
TaprootFreak May 21, 2026
eb066bb
ci: switch heavy jobs to cargo-nextest + sccache (#70)
TaprootFreak May 21, 2026
5145032
ci: auto-label Release PR with ci:full and drop dead push trigger (#72)
TaprootFreak May 21, 2026
67b2634
chore: align DEV and PRD on the MVP-only binary (#73)
TaprootFreak May 22, 2026
c9eb1a1
test(server): add HTTP API e2e suite covering all 15 routes (#74)
TaprootFreak May 22, 2026
51fae01
docs: multi-asset protocol design document (#77)
TaprootFreak May 23, 2026
5239cf5
docs: add Arkade × zkCoins integration design document (#79)
TaprootFreak May 23, 2026
ef5687a
fix(ci): group concurrency by PR number, not commit SHA (#80)
TaprootFreak May 23, 2026
1c25176
refactor(server): mint is permanent MVP (remove faucet feature) (#75)
TaprootFreak May 23, 2026
188b1ef
fix(migrations): restore 0002 to pre-#75 content to repair checksum (…
TaprootFreak May 23, 2026
5bc7d12
test(api_remote): retry second mint on scanner-lag 422 (#83)
TaprootFreak May 23, 2026
003edf2
fix(ci): use host-side reset-zkcoins-server for state reset (#82)
TaprootFreak May 23, 2026
ab4c5b1
ci: pipeline optimizations (coverage decoupling, buildx cache, PRD e2…
TaprootFreak May 23, 2026
0117f66
refactor(server): usernames are permanent MVP (remove usernames featu…
TaprootFreak May 23, 2026
b6014f7
feat(scanner): event-driven chain ingestion (replace Esplora polling)…
TaprootFreak May 23, 2026
0ed6de8
fix(scanner_ws_parse): flatten block arm so llvm-cov tracks line 37 (…
TaprootFreak May 23, 2026
aed1bb2
fix(server): prepare-then-commit mint to prevent state desync (#90)
TaprootFreak May 23, 2026
7e663a3
rename: server → node (full identity rename) (#93)
TaprootFreak May 25, 2026
13155c1
hotfix(migration): revert SQL comment edit to keep sqlx hash stable (…
TaprootFreak May 25, 2026
643fd79
chore(dockerhub): migrate namespace zkcoin → zkcoins (#96)
TaprootFreak May 25, 2026
4cedae5
docs: link Docker Hub repo prominently (#97)
TaprootFreak May 25, 2026
ebb1a30
test: harden suite — remove dev_skip masking + publisher preflight (#94)
TaprootFreak May 25, 2026
e6f7b4c
ci: raise sccache cache cap to 50 GiB (#99)
TaprootFreak May 25, 2026
0ceeeb3
docs: fix residual server → node references missed in #93 (#98)
TaprootFreak May 25, 2026
2278b87
docs(program-plonky2): update server → node refs in migration notes (…
TaprootFreak May 25, 2026
9813af8
docs: reflect 6-agent runner pool + Server→Node rename residuals (#104)
TaprootFreak May 25, 2026
cb2c208
security: require PUBLISHER_KEY env var on every network (no default)…
TaprootFreak May 25, 2026
ca45495
chore(test): belt-and-braces polish on top of PR #94 (#100)
TaprootFreak May 25, 2026
1b3ea03
fix(api-e2e): WS-timeout REST fallback + feature-trimmed-server skip-…
TaprootFreak May 25, 2026
f1af59a
feat(bin): recover_inscription CLI for stuck anchor recovery (#106)
TaprootFreak May 25, 2026
b26a2d1
feat(publisher): persist + auto-resume pending inscriptions (Phase B)…
TaprootFreak May 25, 2026
c5d8f88
feat(state): persist mmr_root_index atomically with state snapshot (P…
TaprootFreak May 25, 2026
1d652e7
refactor(state): derive minting num_pubkeys from SMT (Phase D) (#109)
TaprootFreak May 25, 2026
84549b6
feat(router): mint_handler advances state synchronously (Phase E) (#110)
TaprootFreak May 25, 2026
f3c7246
test(router): align mint error tests with post-Phase-E flow (#111)
TaprootFreak May 25, 2026
2e45c80
fix(ci): add SSH keepalive to deploy-dev + deploy-prd ssh commands (#…
TaprootFreak May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Pre-push gate for zk-coins/node.
#
# Activation (one-time per clone):
# git config core.hooksPath .githooks
#
# This hook catches lint regressions in seconds — there is no point
# waiting for CI to flag a missing import or a misformatted file.
#
# The authoritative test + coverage gate runs in CI on a self-hosted
# M3 Ultra runner (issue #40, .github/workflows/ci.yaml). The hook
# does *not* re-run those — locally on a laptop they take 60-90 min,
# and a developer waiting that long on every push is exactly what
# issue #40 removed.
#
# Wall budgets (warm cache, M3 Ultra) — kept in sync with the matching
# table in CONTRIBUTING.md § Setup:
# - cold cache: < 2 min
# - warm cache: < 30 s
#
# Bypass: `git push --no-verify` works. CI is the real gate, so a
# bypassed lint failure surfaces at the PR check level instead.
set -euo pipefail

echo "[pre-push] cargo fmt --all --check"
cargo fmt --all --check

echo "[pre-push] cargo clippy -p node -p shared (MVP feature set)"
cargo clippy -p node -p shared -- -D warnings

echo "[pre-push] cargo clippy -p node --all-features (self-host opt-in build)"
cargo clippy -p node --all-features -- -D warnings

echo "[pre-push] cargo clippy -p zkcoins-program-plonky2 -p zkcoins-prover-plonky2 --lib"
cargo clippy -p zkcoins-program-plonky2 -p zkcoins-prover-plonky2 --lib -- -D warnings

echo "[pre-push] cargo check --workspace --all-features"
cargo check --workspace --all-features

echo "[pre-push] all checks passed."
11 changes: 11 additions & 0 deletions .github/workflows/auto-release-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
permissions:
contents: read
pull-requests: write
issues: write # required by `gh label create`

concurrency:
group: auto-release-pr
Expand Down Expand Up @@ -58,8 +59,18 @@ jobs:
"- [ ] Merge when ready for production" \
> /tmp/pr-body.md

# `ci:full` opts the PR into the heavy M3 Ultra test + coverage
# jobs (see ci.yaml). Release PRs are exactly when we want the
# authoritative gate, so apply it on creation rather than
# relying on a human to remember the click.
gh label create ci:full \
--color FFA500 \
--description "Run heavy M3 Ultra test + coverage jobs on this PR" \
2>/dev/null || true

gh pr create \
--base main \
--head develop \
--title "Release: develop -> main" \
--label ci:full \
--body-file /tmp/pr-body.md
420 changes: 320 additions & 100 deletions .github/workflows/ci.yaml

Large diffs are not rendered by default.

187 changes: 182 additions & 5 deletions .github/workflows/deploy-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,20 @@ on:
type: boolean
default: false

# Serialize DEV deploys per branch. Multiple develop pushes in quick
# succession (e.g. three PRs merged back-to-back) used to fire three
# parallel deploys that raced on `docker compose recreate` on the
# host and left the zkcoins-node container half-renamed in
# `Created` state, blocking the next `up -d` with a name conflict.
# `cancel-in-progress: true` keeps the newest commit's deploy; the
# older deploy is irrelevant the moment its commit is no longer the
# branch tip.
concurrency:
group: deploy-dev
cancel-in-progress: true

env:
DOCKER_TAGS: zkcoin/server:beta
DOCKER_TAGS: zkcoins/node:beta

permissions:
contents: read
Expand Down Expand Up @@ -41,8 +53,18 @@ jobs:
push: true
tags: ${{ env.DOCKER_TAGS }}
platforms: linux/arm64
build-args: |
FEATURES=address-list,faucet,usernames,lnurl
# Registry-backed buildx cache. Same `zkcoins/node:buildcache`
# tag is reused by Deploy PRD — DEV and PRD compile the same
# Rust workspace so cache hits cross-deploy. `type=registry`
# over `type=gha` because GHA cache caps at 10 GB with LRU
# eviction; Docker Hub holds the tag indefinitely.
# Caveat: DEV's `cancel-in-progress: true` (above) can interrupt
# a concurrent DEV deploy mid-push to the cache manifest.
# BuildKit's `cache-from` tolerates partial manifests (falls back
# to a from-scratch build with a warning) so the race is
# self-healing on the next deploy.
cache-from: type=registry,ref=zkcoins/node:buildcache
cache-to: type=registry,ref=zkcoins/node:buildcache,mode=max

- name: Install cloudflared
run: |
Expand All @@ -56,12 +78,167 @@ jobs:
chmod 600 ~/.ssh/deploy_key
echo "${{ secrets.DEPLOY_DEV_SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts

DEPLOY_CMD="zkcoins-server"
# The deploy host runs a forced-command restricted shell that only
# accepts whitelisted command names — arbitrary inline shell is
# rejected. Both branches must resolve to a single allowlisted
# command; the reset variant is implemented host-side.
DEPLOY_CMD="zkcoins-node"
if [ "${{ inputs.reset_state }}" == "true" ]; then
DEPLOY_CMD="cd ~/zkcoins && docker compose stop zkcoins-server && docker compose rm -f zkcoins-server && docker volume rm zkcoins_server-data 2>/dev/null; zkcoins-server"
DEPLOY_CMD="reset-zkcoins-node"
fi

# ServerAlive* keep the session alive across long-running
# `docker compose recreate` steps where the remote command
# produces no stdout for >60s. Without keepalive the
# cloudflared tunnel (and the OpenSSH client) drop the
# session and exit 255 even though the host-side deploy
# script keeps running — observed on the PR #111 merge
# (run 26419696840). 30s interval × 8 retries = 4 min of
# network silence tolerated before the session is killed.
ssh -i ~/.ssh/deploy_key \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=8 \
-o ProxyCommand="cloudflared access ssh --hostname ${{ secrets.DEPLOY_DEV_HOST }}" \
${{ secrets.DEPLOY_DEV_USER }}@${{ secrets.DEPLOY_DEV_HOST }} \
"$DEPLOY_CMD"

# Post-deploy smoke test: hit the public endpoint until /api/info
# answers 200 or we give up. A green "Build and deploy to DEV"
# without this step was historically misleading — a runtime-bootstrap
# panic left the container Up-but-unresponsive while the workflow
# reported success. Failing this step blocks the auto-release PR
# from collecting a green check and surfaces the regression in CI.
- name: Smoke test public endpoint
run: |
set -euo pipefail
URL="https://dev-api.zkcoins.app/api/info"
for i in $(seq 1 30); do
code=$(curl -sS -o /tmp/info.json -w '%{http_code}' --max-time 10 "$URL" || echo "000")
if [ "$code" = "200" ]; then
echo "DEV /api/info responded 200 after ${i} attempt(s):"
cat /tmp/info.json
echo
exit 0
fi
echo "[$i/30] $URL -> ${code} (waiting 10 s)"
sleep 10
done
echo "::error::DEV /api/info never returned 200 within ~5 min after deploy"
exit 1

# Functional verification of the deployed DEV server.
#
# The smoke test in `build-and-deploy` only proves the HTTP listener
# is bound; this job exercises all 15 routes end-to-end (read-only,
# negative-path, full mint→send→commit and username-claim roundtrips
# against the live server). Runs on the same self-hosted M3 Ultra
# runner as `node-tests` / `coverage`, so sccache hits the warm
# cache populated by previous runs and the build itself stays
# well under a minute on a hot cache.
api-e2e:
name: API E2E against DEV
needs: build-and-deploy
runs-on: [self-hosted, m3-ultra]
timeout-minutes: 30
env:
RUSTC_WRAPPER: sccache
ZKCOINS_API_URL: https://dev-api.zkcoins.app
# The bootstrap `lazy_static`s panic if these are unset; the
# integration test only talks to the deployed server but the
# lib's panic-on-load behaviour is unconditional. Values are
# placeholders — nothing in the test path reads them.
USERNAME_DOMAIN: dev.zkcoins.app
ESPLORA_URL: http://127.0.0.1:1/api
steps:
- name: Checkout
uses: actions/checkout@v4

# Self-hosted runner inherits a minimal PATH that hides rustup;
# see the matching step in `node-tests` for the rationale.
- name: Prepend ~/.cargo/bin to PATH (use rustup proxy, not Homebrew Rust)
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"

- name: Ensure sccache + cargo-nextest are installed
run: |
command -v sccache >/dev/null || brew install sccache
command -v cargo-nextest >/dev/null || brew install cargo-nextest
sccache --start-server >/dev/null 2>&1 || true
sccache --show-stats

# Operational preflight: hit /health/ready and /health/publisher
# BEFORE running the API E2E suite, so an empty publisher wallet
# or a non-ready DB fails THIS step with a clear "top up the
# publisher" / "DB not ready" message instead of cascading
# through the test suite as opaque 503s.
#
# Historically a green E2E run masked an empty publisher wallet
# because the suite silently dev_skip!()'d 5xx errors; PR
# "test: harden suite" (this PR) removed the masking and added
# this preflight as the load-bearing operational gate.
#
# 50_000 sats is a conservative floor: a single inscription
# commit + reveal pair at typical Mutinynet fee rates needs
# ~1_500 sats; 50_000 buys ~30 mints before the next top-up.
# Adjust upward if the suite grows.
- name: Ensure jq is installed (preflight dependency)
run: command -v jq >/dev/null || brew install jq

- name: Preflight — publisher wallet has UTXOs
env:
DEV_API: https://dev-api.zkcoins.app
run: |
set -euo pipefail
ready=$(curl -sS --max-time 10 "$DEV_API/health/ready")
if ! echo "$ready" | jq -e '.ready == true' > /dev/null; then
echo "::error::/health/ready not ready: $ready"
exit 1
fi
pub=$(curl -sS --max-time 15 -w '|%{http_code}' "$DEV_API/health/publisher")
code="${pub##*|}"
body="${pub%|*}"
if [ "$code" != "200" ]; then
echo "::error::/health/publisher returned $code: $body"
exit 1
fi
utxos=$(echo "$body" | jq -r '.utxo_count')
sats=$(echo "$body" | jq -r '.total_sats')
if [ "$utxos" -lt 1 ] || [ "$sats" -lt 50000 ]; then
echo "::error::publisher wallet too low (utxos=$utxos, sats=$sats) — top up before re-running"
exit 1
fi
echo "publisher OK: utxos=$utxos, sats=$sats"

- name: Run API E2E suite against DEV
env:
# DEV image is MVP-only by policy (see Dockerfile FEATURES
# arg); the gated address-list/lnurl tests skip cleanly
# instead of panicking the CI canary.
ZKCOINS_E2E_ALLOW_FEATURE_TRIMMED_SERVER: "true"
run: cargo test -p node --release --all-features --test api_remote -- --test-threads=1 --nocapture

- name: sccache stats (post-build)
if: always()
run: sccache --show-stats

# Telegram alert on workflow failure. Separate job (not an inline step)
# so job-level failures — timeout, OOM, runner crash — still fire the
# alert; runs on the cheapest runner since the curl never needs to touch
# the self-hosted M3 Ultra. See ci.yaml > notify-failure for the
# firing-matrix rationale.
notify-failure:
name: Telegram alert on failure
needs: [build-and-deploy, api-e2e]
if: failure()
runs-on: ubuntu-latest
steps:
- name: Send Telegram alert
env:
TG_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TG_CHAT: ${{ secrets.TELEGRAM_CHAT_ID }}
run: |
TEXT=$'❌ <b>'"${{ github.workflow }}"$'</b> failed\n<b>Repo:</b> '"${{ github.repository }}"$'\n<b>Branch:</b> '"${{ github.ref_name }}"$'\n<b>Run:</b> '"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
curl -sS -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${TG_CHAT}" \
--data-urlencode "text=${TEXT}" \
-d "parse_mode=HTML" \
-d "disable_web_page_preview=true"
Loading
Loading