Skip to content

Latest commit

 

History

History
270 lines (213 loc) · 11.8 KB

File metadata and controls

270 lines (213 loc) · 11.8 KB

Local Development

How to run Guardian on a developer machine, what choices you have, and which example to reach for once it's up.

For protocol concepts (State, Delta, Nonce, Commitment) read spec/index.md first — this guide assumes you know them. For the deployed AWS topology see docs/architecture/infra.md.

What you're choosing

Three decisions when running Guardian locally:

  1. Storage backend — filesystem (default) or Postgres. See Storage modes. Filesystem is fine for most local work; pick Postgres only if you are testing migrations, audit persistence, or multi-replica behavior.
  2. Cargo featurespostgres and/or evm. Default builds do not include EVM routes and do not include the Postgres backend.
  3. How to launchcargo run (fastest iteration) or docker compose (closer to the deployed shape).

Prerequisites

  • Rust toolchain pinned by rust-toolchain.toml.
  • Node 18+ if you will run any TS examples or packages.
  • Docker if you will use docker-compose.*.yml.
  • A Miden node — required for almost every flow. Either point at a Miden Devnet endpoint or run one locally; configure via GUARDIAN_NETWORK_TYPE. A node on a non-default host or port (for example a sidecar container) is reachable via GUARDIAN_MIDEN_RPC_ENDPOINT without changing the network type; see CONFIGURATION.md for that and the optional GUARDIAN_MIDEN_RPC_TIMEOUT_MS / GUARDIAN_MIDEN_RPC_MAX_ATTEMPTS read-retry knobs. The node's Miden line must match the workspace baseline (currently the 0.16 pre-release line; devnet already runs the 0.16 node) — a mismatched node is rejected at the RPC boundary (see Troubleshooting).

Environment file

The server calls dotenvy::dotenv() on startup, so cargo run --bin server automatically reads a root .env file when one exists. In practice a .env file (or exported variables) is required: GUARDIAN_NETWORK_TYPE has no default and the server refuses to start without it, and the built-in filesystem paths live under /var/guardian, which may not exist or be writable on a developer machine.

Minimal local .env:

GUARDIAN_STORAGE_PATH=.guardian/storage
GUARDIAN_METADATA_PATH=.guardian/metadata
GUARDIAN_KEYSTORE_PATH=.guardian/keystore
GUARDIAN_NETWORK_TYPE=MidenDevnet
RUST_LOG=info

Create the directories once before the first run:

mkdir -p .guardian/storage .guardian/metadata .guardian/keystore

Use .env.example as a broader template when you need deploy variables, Postgres, dashboard operators, or EVM settings. Docker Compose does not inject the root .env into the server container by default; the checked-in compose file already sets the container filesystem paths.

Path A — cargo run with filesystem (fastest)

cargo run --bin server

This builds with no extra features and uses the filesystem backend. Useful env:

Variable Notes
GUARDIAN_STORAGE_PATH Local path for state + deltas. Defaults to /var/guardian/storage.
GUARDIAN_METADATA_PATH Local path for accounts, auth, network. Defaults to /var/guardian/metadata.
GUARDIAN_KEYSTORE_PATH ACK key files, auto-generated on first run. Defaults to /var/guardian/keystore.
RUST_LOG (info) info, debug, or e.g. server::jobs::canonicalization=debug.
GUARDIAN_NETWORK_TYPE (required) Miden network name: MidenLocal, MidenTestnet, or MidenDevnet. The server refuses to start when it is unset or unrecognized.

At startup the server emits a warning that audit events will not be persisted — that's expected for filesystem mode (builder/storage.rs:133).

The HTTP server binds on :3000, gRPC on :50051.

Path B — cargo run with Postgres

docker compose -f docker-compose.postgres.yml up -d

DATABASE_URL=postgres://guardian:guardian@localhost:5432/guardian \
  cargo run -p guardian-server --features postgres --bin server

The Postgres path runs SQL migrations on startup (builder/storage.rs) and wires PostgresAuditor so admin actions land in the admin_actions table. Pool sizing is controlled by GUARDIAN_DB_POOL_MAX_SIZE and GUARDIAN_METADATA_DB_POOL_MAX_SIZE.

Postgres-backed tests

The guardian-server tests that need a live database (metadata replay state, delta storage fencing, the audit append-only trigger, and the lease, session, and challenge coordination stores) stay #[ignore] and run through one script:

POSTGRES_PASSWORD=guardian docker compose -f docker-compose.postgres.yml up -d postgres
./scripts/test-postgres.sh

Name the postgres service explicitly: an unqualified up -d also builds and starts the server image, which these tests do not use. POSTGRES_PASSWORD is required even when only the database service is selected, because Compose interpolates the whole file; the script's default connection URL expects guardian. If your Compose volume was initialised with a different password, pass a matching DATABASE_URL instead of relying on the default.

The suite drops and recreates the public schema before the first test and re-applies every migration from empty, so no run inherits state from an earlier one and there is no cleanup step. It creates guardian_test if it is missing, and it refuses to run against a database whose name does not end in _test, so a DATABASE_URL still exported from a Path B session cannot lose your development data. Point it elsewhere with:

DATABASE_URL=postgres://user:pass@host:5432/guardian_test ./scripts/test-postgres.sh

Run one suite at a time per server: the reset wipes the shared test database. Execution is serial because one test reverts the newest migration for the duration of its own run.

The local compose Postgres uses no TLS, so omit sslmode (plaintext). To exercise certificate verification locally, run a TLS-enabled Postgres with a self-signed CA and point the server at it:

# Generate a CA and a server cert whose SAN matches the connection host
openssl req -x509 -newkey rsa:2048 -nodes -keyout ca.key -out ca.pem \
  -subj "/CN=Test CA" -days 3650
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr \
  -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost"
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial \
  -out server.crt -days 3650 -copy_extensions copyall
# start postgres with ssl=on using server.crt/server.key, then:

# verify-full: chain + hostname (SAN must be localhost)
DATABASE_URL="postgres://guardian:guardian@localhost:5432/guardian?sslmode=verify-full&sslrootcert=$PWD/ca.pem" \
  cargo run -p guardian-server --features postgres --bin server

# verify-ca: chain only (hostname not checked)
DATABASE_URL="postgres://guardian:guardian@localhost:5432/guardian?sslmode=verify-ca&sslrootcert=$PWD/ca.pem" \
  cargo run -p guardian-server --features postgres --bin server

Expected: correct CA + matching SAN → starts; wrong/empty CA file → fails fast with a certificate error; under verify-full a SAN ≠ localhost is refused while the same cert is accepted under verify-ca. See the full matrix in CONFIGURATION.md → Database TLS.

Path C — cargo run with EVM support

GUARDIAN_EVM_RPC_URLS=31337=http://127.0.0.1:8545 \
GUARDIAN_EVM_ENTRYPOINT_ADDRESS=0x... \
  cargo run -p guardian-server --features evm --bin server

EVM routes (/evm/auth/*, /evm/accounts, /evm/proposals*) only register when the evm feature is on. Combine with postgres for prod-like local setups: --features postgres,evm. Pair with an Anvil node — the smoke-test-evm-proposal-support skill walks through the full flow.

Path D — Docker Compose

docker compose up --build -d
docker compose logs -f

This is the default Compose flow — filesystem backend, no Postgres, no root .env required. For a Postgres-backed compose stack use docker-compose.postgres.yml. Endpoints are the same as Path A (:3000, :50051).

Choosing a feature flag combo

Goal Features Backend
Hack on a service handler quickly none filesystem
Touch migrations, audit, or multi-replica behavior postgres Postgres
Exercise the EVM proposal flow evm filesystem
Reproduce a prod issue locally postgres,evm Postgres
Run the operator dashboard any either (Postgres for durable history)

The deploy script builds with postgres,evm when the EVM stack is requested — see SERVER_AWS_DEPLOY.md.

Verifying the server is up

curl http://localhost:3000/                   # liveness (alias of /status)
curl http://localhost:3000/pubkey             # ACK key commitment
grpcurl -plaintext \
  -import-path crates/server/proto -proto guardian.proto \
  -d '{}' localhost:50051 guardian.Guardian/GetPubkey

If GetPubkey returns a key, the server is wired correctly and the gRPC target group's health check (infra/alb.tf:55) would pass in production.

Reaching for an example

Example What it exercises SDK
examples/demo End-to-end multisig flow in a Rust TUI — recommended starting point. Rust multisig client
examples/rust Low-level Rust binaries for both local-node and mockchain flows. Rust client
examples/smoke-web Browser harness for multisig + wallet integrations. TS multisig client
examples/operator-smoke-web Local Falcon operator login + dashboard account APIs. @openzeppelin/guardian-operator-client
examples/evm-smoke-web EVM proposal lifecycle against Anvil + an EVM-enabled server. @openzeppelin/guardian-evm-client
examples/web Reference web integration. TS multisig client

Follow each example's README to drive it manually. Agents in this repo have matching skills (smoke-test-rust-multisig-sdk, smoke-test-ts-multisig-sdk, smoke-test-operator-dashboard, smoke-test-evm-proposal-support) that automate the same flows.

Running tests

cargo test --workspace
cargo test -p guardian-server --features integration
cargo test -p guardian-server --features e2e

TypeScript packages each carry their own npm test — see the root README.md.

Cargo feature gates (integration, e2e) document what each suite needs at the top of the relevant test modules under crates/server/src/testing. Agents in this repo have a guardian-validation-matrix skill that picks the smallest meaningful set for a given change.

Common gotchas

  • DATABASE_URL missing under --features postgres — the builder fails fast with "DATABASE_URL environment variable is required" (builder/storage.rs:97).
  • Filesystem dirs don't exist — the server creates them on demand, but the parent path must be writable.
  • ACK keypair changes between runs in filesystem mode — keys auto-generate on first start. If clients pinned an old pubkey, point them at the new one or persist the keystore directory.
  • gRPC reflection over Cloudflare — works locally but Cloudflare's free tier rejects gRPC unless explicitly enabled on the zone.
  • Apple Silicon + --platform linux/amd64 — slow but works. For faster local Docker builds, build linux/arm64 images and deploy with cpu_architecture = "ARM64".