Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"crates/mina-verify-cli",
"crates/mina-verify-monitor",
"crates/mina-verify-wasm",
"crates/mina-verify-server",
]

# Pinned to o1-labs/mina-rust @ ab69eaed. NOTE: upstream (OpenMina) is unmaintained;
Expand Down
9 changes: 7 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ RUN cargo build --release --locked
RUN strip \
target/release/mina-verify \
target/release/mina-verify-capture \
target/release/mina-verify-monitor
target/release/mina-verify-monitor \
target/release/mina-verify-server

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates libssl3 \
ca-certificates libssl3 curl \
&& rm -rf /var/lib/apt/lists/*
# curl: for the verify-server container healthcheck (GET /health)
COPY --from=builder /src/target/release/mina-verify /usr/local/bin/mina-verify
COPY --from=builder /src/target/release/mina-verify-capture /usr/local/bin/mina-verify-capture
COPY --from=builder /src/target/release/mina-verify-monitor /usr/local/bin/mina-verify-monitor
COPY --from=builder /src/target/release/mina-verify-server /usr/local/bin/mina-verify-server
# default: the live verify-before-ingest monitor (MINA_NETWORK env selects the network)
# Run the verify HTTP sidecar instead with:
# docker run -e MINA_NETWORK=devnet -p 8090:8090 --entrypoint mina-verify-server <image>
ENTRYPOINT ["mina-verify-monitor"]
17 changes: 17 additions & 0 deletions crates/mina-verify-server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "mina-verify-server"
version = "0.0.1"
edition = "2021"
description = "HTTP verify sidecar: POST a precomputed block, get its proof-backed facts."
license = "Apache-2.0"

[dependencies]
# Only the verify library — no networking (the precomputed path needs none), so this
# stays a small, CPU-bound, stateless service.
mina-verify = { path = "../mina-verify" }
# Synchronous HTTP server: verification is blocking CPU work, so a thread-per-worker
# model fits better than async (no tokio needed).
tiny_http = "0.12"
serde_json = { workspace = true }
log = { workspace = true }
env_logger = { workspace = true }
86 changes: 86 additions & 0 deletions crates/mina-verify-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# mina-verify-server

A long-lived HTTP **verify sidecar**: POST a precomputed block, get its proof-backed
facts. Built for a **trustless indexer** — verify each block's SNARK proof before
ingesting it, with no trusted daemon. A valid proof attests the entire chain to genesis
by recursion, so trust in whoever produced the block is not required.

Only the `mina-verify` library is linked (no networking — the precomputed path needs
none), so this stays a small, stateless, CPU-bound service. The expensive verifier setup
is paid once at startup; each request is just the proof check.

## Endpoints

```
GET /health -> { "status": "ok", "network": "devnet" }

POST /verify (body = precomputed-block JSON)
proof valid -> 200 { "valid": true, "height", "state_hash",
"previous_state_hash", "staged_ledger_hash" }
proof invalid -> 200 { "valid": false, "error": "block proof did not verify" }
undecodable -> 400 { "valid": false, "error": "<detail>" }
```

The caller ingests iff `valid` is `true`, keyed by the returned (proof-backed) hashes.

> Real precomputed blocks are **not strictly UTF-8** — the OCaml daemon emits some
> byte-string fields (e.g. `sok_digest` in `staged_ledger_diff`) as mixed raw/escaped
> bytes. Those fields are ignored by verification, so the body is decoded lossily; this is
> expected and harmless.

## Config (env)

| var | meaning | default |
|-----|---------|---------|
| `BIND` | listen address | `0.0.0.0:8090` |
| `MINA_VK_JSON` | path to a blockchain verifier-index JSON (any network; **required for mesa / mesa-mut**). Takes precedence. | — |
| `MINA_NETWORK` | embedded-VK network when `MINA_VK_JSON` is unset (`devnet` / `mainnet`) | `devnet` |
| `VERIFY_THREADS` | worker threads (verification is CPU-bound) | available parallelism |

## Run

```sh
# native
MINA_NETWORK=devnet cargo run --release -p mina-verify-server

# docker (image builds all mina-verify bins; override the entrypoint)
docker run -e MINA_NETWORK=devnet -p 8090:8090 --entrypoint mina-verify-server <image>

curl -s localhost:8090/health
curl -s -X POST --data-binary @block.json localhost:8090/verify
```

Verification on a native release build is ~1–2 s per block, so an indexer can afford to
verify **every** block (not just the tip).

## Trustless-indexer topology

```yaml
# docker-compose.yml
services:
mina-verifier:
image: mina-verify
entrypoint: mina-verify-server
environment:
MINA_NETWORK: devnet # or mount a VK and set MINA_VK_JSON for mesa
# ports/healthcheck omitted for brevity

mina-indexer:
image: mina-indexer
# reuse the existing exe-hook pattern: --verify-block-exe points at a shim that
# curls http://mina-verifier:8090/verify and exits non-zero unless {"valid":true}
depends_on: [mina-verifier]
```

`verify-block.sh` shim (mirrors the indexer's `--fetch-new-blocks-exe` convention):

```sh
#!/usr/bin/env bash
# usage: verify-block.sh <precomputed-block.json> — exit 0 iff the proof verifies
set -euo pipefail
curl -fsS -X POST --data-binary @"$1" "${VERIFIER_URL:-http://mina-verifier:8090}/verify" \
| grep -q '"valid":true'
```

The sidecar's Rust toolchain (1.94.1 + mina-verify's patched lock) stays bottled up in its
own container — it never touches the indexer's build.
37 changes: 37 additions & 0 deletions crates/mina-verify-server/deploy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Trustless-indexer deployment

Glue for gating an indexer's ingestion on the verify sidecar.

- **`docker-compose.yml`** — runs `mina-verifier` (the sidecar) with a `/health`
healthcheck, plus a commented `mina-indexer` stub showing the wiring.
- **`verify-block.sh`** — the `--verify-block-exe` shim: `curl`s the sidecar's
`/verify` and exits `0` iff `{"valid":true}`. The indexer calls it at canonical
promotion; non-zero ⇒ reject the block.

## Try the gating contract (devnet, no indexer needed)

```sh
# 1. start the sidecar (native or container)
MINA_NETWORK=devnet cargo run --release -p mina-verify-server # :8090
# or: docker compose -f docker-compose.yml up mina-verifier

# 2. point the shim at it and gate a real block vs a tampered one
VERIFIER_URL=http://127.0.0.1:8090 ./verify-block.sh real-block.json ; echo "exit=$?" # 0 → ingest
VERIFIER_URL=http://127.0.0.1:8090 ./verify-block.sh tampered.json ; echo "exit=$?" # !0 → reject
```

## Wiring into the indexer

The indexer reuses its existing exe-hook pattern (like `--fetch-new-blocks-exe`):
pass `--verify-block-exe /app/verify-block.sh` and set `VERIFIER_URL` to the sidecar.
At canonical extension the indexer shells out per block; `valid:true` ⇒ persist,
otherwise reject and log. Gate only at canonical promotion (not losing forks), and
on a mainnet-scale backfill verify the tip + checkpoints rather than every historical
block (recursion already backs the ancestry). At mesa scale, verify-all is fine
(~1–2 s/block).

## Networks

devnet/mainnet use the embedded VK. For **mesa / mesa-mut**, mount a verifier-index
JSON into the sidecar and set `MINA_VK_JSON` (see the compose comments) — same shim,
same demo, no code change.
41 changes: 41 additions & 0 deletions crates/mina-verify-server/deploy/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Trustless-indexer topology: the indexer gates ingestion on the verify sidecar.
#
# docker build -t mina-verify:sidecar ../../.. # from this repo root
# docker compose -f crates/mina-verify-server/deploy/docker-compose.yml up
#
# devnet/mainnet work out of the box (embedded VK). For mesa/mesa-mut, mount a
# verifier-index JSON and set MINA_VK_JSON instead of MINA_NETWORK.
services:
mina-verifier:
image: mina-verify:sidecar
entrypoint: mina-verify-server
environment:
MINA_NETWORK: devnet
BIND: 0.0.0.0:8090
# mesa / mesa-mut (no embedded VK): mount the index and use it instead —
# MINA_VK_JSON: /vk/mesa_blockchain_verifier_index.json
# volumes:
# - ./mesa_blockchain_verifier_index.json:/vk/mesa_blockchain_verifier_index.json:ro
ports:
- "8090:8090"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8090/health | grep -q ok"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

# The indexer image gates canonical-block ingestion on the verifier. Stubbed — wire
# your indexer image and flags here. The shim (verify-block.sh) is mounted in and
# passed as --verify-block-exe; VERIFIER_URL points it at the sidecar.
#
# mina-indexer:
# image: mina-indexer
# environment:
# VERIFIER_URL: http://mina-verifier:8090
# volumes:
# - ./verify-block.sh:/app/verify-block.sh:ro
# command: ["mina-indexer", "...", "--verify-block-exe", "/app/verify-block.sh"]
# depends_on:
# mina-verifier:
# condition: service_healthy
24 changes: 24 additions & 0 deletions crates/mina-verify-server/deploy/verify-block.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Indexer --verify-block-exe shim: exit 0 iff the block's SNARK proof verifies.
#
# At canonical promotion the indexer calls this with the precomputed-block file; a
# non-zero exit means "reject — do not ingest". Mirrors the --fetch-new-blocks-exe
# convention (a thin curl wrapper, like mesa-pull), so the warm verifier lives in the
# sidecar and the indexer stays toolchain-clean.
#
# usage: verify-block.sh <precomputed-block.json>
# env: VERIFIER_URL (default http://mina-verifier:8090)
# VERIFY_TIMEOUT seconds (default 120)
set -euo pipefail

block="${1:?usage: verify-block.sh <precomputed-block.json>}"
url="${VERIFIER_URL:-http://mina-verifier:8090}/verify"

# curl -f => non-zero on HTTP errors; set -e => abort the script on that.
resp="$(curl -fsS --max-time "${VERIFY_TIMEOUT:-120}" \
-H 'Content-Type: application/json' --data-binary @"$block" "$url")"

echo "verify-block: $resp" >&2 # surface the verdict in indexer logs

# Final command's status is the script's exit status: 0 iff the proof verified.
printf '%s' "$resp" | grep -q '"valid":true'
Loading
Loading