From 12d9db4978db75f3d52962b3eb73d9d57a4dd101 Mon Sep 17 00:00:00 2001 From: bdimitrov-netzine Date: Fri, 21 Aug 2026 10:29:22 +0300 Subject: [PATCH 1/7] feat: update to latest version of services --- DEV_MODE.md | 75 +++++++++----- README.md | 44 ++++---- cmd/dev.go | 2 +- cmd/init.go | 2 +- internal/docker/docker_config.go | 57 +++++++--- internal/docker/docker_config_local_test.go | 85 +++++++++++++-- internal/docker/docker_config_nohub_test.go | 6 +- internal/docker/docker_config_pull_test.go | 12 ++- internal/docker/sources.go | 109 ++++++++++++++++---- internal/docker/sources_test.go | 27 ++--- internal/stacks/dev.go | 14 ++- internal/stacks/dev_test.go | 4 +- internal/stacks/nohub_test.go | 6 +- internal/stacks/publicchainkey_test.go | 4 +- internal/stacks/stack_manager.go | 29 +++--- 15 files changed, 343 insertions(+), 133 deletions(-) diff --git a/DEV_MODE.md b/DEV_MODE.md index 4e76cca..f9aa780 100644 --- a/DEV_MODE.md +++ b/DEV_MODE.md @@ -24,17 +24,37 @@ Components and the refs they build from (`internal/docker/sources.go`): | Component (`Key`) | Repo | Default ref | Images built | |---|---|---|---| -| `contracts` | `rayls-privacy-contracts` | `lean-no-pnh-3.0.0` | `contracts` (deploy tooling) | -| `relayer` | `rayls-privacy-relayer-api` | `v3.0.0` | `kos` (CTS) + `pubrelayer` | -| `backend` | `rayls-privacy-backend` | `v3.0.0` | `backend` | - -Infra images (`mongodb`, `nats`, `private-network-hub`, `axyl` privacy node) are -**not** source-built — they come from their normal images. - -Because the repos are private, git contexts use SSH (`git@github.com:...`), so the -build forwards your **ssh-agent** (`build.ssh: [default]`). Make sure your key is -loaded (`ssh-add -l`). Once the repos are public over https this drops out -automatically. +| `contracts` | `rayls-sovereign-contracts` | `main` | `contracts` (deploy tooling) | +| `relayer` | `rayls-sovereign-relayer` | `main` | `kos` (CTS) + `pubrelayer` + `relayer` (private) | +| `governance` | `rayls-sovereign-pnh-governance` | `main` | `governance-api` + `-listener` + `-flagger` | +| `gnark` | `rayls-sovereign-gnark-api` | `main` | `proofs-api` (Enygma proofs) | +| `auditor` | `rayls-sovereign-pnh-auditor-ui` | `main` | `audit-explorer` (Angular → nginx) | + +The `rayls-sovereign-*` repos hold the **3.0.1** code as a single `main` branch +(no version tags yet), so `main` is the default build ref. `governance`, `gnark` +and the private `relayer` only appear in hub topologies (`--with-hub` / `--full`); +`auditor` is `--full` only. The only Rayls images **not** source-built are the +infra ones — `nats`, `private-network-hub` (Besu) — plus the `axyl` privacy node +(pulled + retagged); everything else builds from the repos above. + +> ⚠️ **gnark uses Git-LFS.** Its proving/verifying keys under `last_build/` are +> Git-LFS blobs. A pinned **git-context** build (the default `--local`) does *not* +> smudge LFS, so `proofs-api` would ship pointer files and fail to load keys. For +> a working Enygma stack, either `rayls dev gnark` (clones with `git lfs pull` — a +> local checkout, so LFS is smudged) or use the pulled ECR image. Non-Enygma +> stacks (the hub-less default) never build `proofs-api`, so this doesn't apply. + +> **ops-api excluded on purpose.** The new `rayls-sovereign-ops-api` (the former +> backend) is not run by the CLI and is not in the registry. + +> ⚠️ **The sovereign repos are currently PRIVATE**, so the default git contexts +> use **SSH** (`git@github.com:…`) and the build forwards your **ssh-agent** +> (`build.ssh: [default]`, added automatically for git@/ssh:// URLs). Make sure a +> key with `raylsnetwork` access is loaded — `ssh-add -l` must list one (if it's +> empty, `ssh-add ~/.ssh/id_ed25519` or your key). **TODO:** once the admin makes +> the repos public, switch the `Repo` URLs in `internal/docker/sources.go` back +> to `https://github.com/raylsnetwork/.git` — the build then clones +> anonymously and drops the ssh-agent requirement. ### Overriding refs / repos (per stack, via `.env`) @@ -47,13 +67,14 @@ repo: _REPO > the registry default src: _SRC (a local checkout path or any git URL; managed by `rayls dev`) ``` -`` is `CONTRACTS`, `RELAYER`, or `BACKEND`. Examples (edit `.env` in the -stack dir — no regeneration needed, compose interpolates at build time): +`` is `CONTRACTS`, `RELAYER`, `GOVERNANCE`, `GNARK`, or `AUDITOR`. +Examples (edit `.env` in the stack dir — no regeneration needed, compose +interpolates at build time): ```dotenv -RELAYER_REF=version/3.0.1 # build the relayer from a different branch -BACKEND_REPO=git@github.com:you/rayls-privacy-backend.git # build from your fork -RAYLS_VERSION=3.0.0 # sets the default ref to v3.0.0 for all components +RELAYER_REF=my-feature-branch # build the relayer from a different branch +CONTRACTS_REPO=git@github.com:you/rayls-sovereign-contracts.git # build from your fork +RAYLS_VERSION=3.0.2 # default ref -> v3.0.2 for all components (needs matching vX.Y.Z tags; the sovereign repos have none yet) ``` --- @@ -75,12 +96,12 @@ checkout you can edit**. It: chain, NATS state) keeps running. ```bash -rayls dev backend # hack on the backend (clones if needed) -rayls dev relayer backend # both at once -rayls dev backend --repo git@github.com:you/rayls-privacy-backend.git # your fork -rayls dev relayer --src ~/code/rayls-privacy-relayer-api # existing checkout +rayls dev relayer # hack on the relayer (clones if needed) +rayls dev relayer contracts # both at once +rayls dev relayer --repo git@github.com:you/rayls-sovereign-relayer.git # your fork +rayls dev relayer --src ~/code/rayls-sovereign-relayer # existing checkout rayls dev --status # which components are in dev mode? -rayls dev --off backend # back to the pinned from-source build +rayls dev --off relayer # back to the pinned from-source build ``` `--repo` / `--src` apply to a **single** component at a time. @@ -89,9 +110,11 @@ rayls dev --off backend # back to the pinned from-source build | Component | Hot reload (`Watch`) | Notes | |---|---|---| -| `relayer` (kos + pubrelayer) | ✅ | air rebuilds on save | -| `backend` | ✅ | air rebuilds on save | +| `relayer` (kos + pubrelayer + private relayer) | ✅ | air rebuilds on save | | `contracts` | ❌ | builds from your checkout, but **redeploys stay explicit** — a file watcher must not silently redeploy contracts | +| `governance` | ❌ | builds from your checkout (production Dockerfiles); rebuild to apply | +| `gnark` | ❌ | builds from your checkout; needs Git-LFS (see the ⚠️ note above) | +| `auditor` | ❌ | Angular → nginx build from your checkout; rebuild to apply | --- @@ -116,10 +139,10 @@ supported for watch. ```bash rayls init --local # 1. build the whole stack from pinned source -rayls dev backend # 2. clone + switch the backend to a local checkout -rayls watch # 3. edit backend source; saves hot-reload in ~seconds +rayls dev relayer # 2. clone + switch the relayer to a local checkout +rayls watch # 3. edit relayer source; saves hot-reload in ~seconds # ...iterate... -rayls dev --off backend # 4. done — back to the pinned build +rayls dev --off relayer # 4. done — back to the pinned build ``` For **contracts**, step 3 is different: edit your checkout, then trigger a diff --git a/README.md b/README.md index 6c50299..bd84a3e 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ If a `docker-compose.yaml` already exists, you'll be prompted to overwrite or us **Options:** * `--full`: Bring up the full multi-participant demo stack (local Private Network Hub / commit chain, governance, proofs API, multiple privacy nodes). Combine with `--members` and/or `--public-chain`. -* `--with-hub`: Include the **Private Network Hub** in the default (lean) stack — a **functional hub**: PNH plus the private relayer and proofs-api, so PN↔PNH messaging and Enygma work. Without it, **hub-less is the default**: no PNH, no private relayer, no proofs API — the privacy nodes intercommunicate via the public chain only. Hub-less currently applies to `--local` stacks only: it needs `rayls-privacy-contracts` / `rayls-privacy-relayer-api` `version/3.0.1`, which the published ECR images predate, so pulled-image inits keep the hub until the images catch up. Every `--local` init records its build sources in the stack `.env` — the local sibling checkouts (`../rayls-privacy-contracts`, `../rayls-privacy-relayer-api`, recorded as relative paths) via `CONTRACTS_SRC`/`RELAYER_SRC` when present (whatever branch is checked out there is what builds), else the `version/3.0.1` git contexts via `CONTRACTS_REF`/`RELAYER_REF`. `--full` always includes the full hub. +* `--with-hub`: Include the **Private Network Hub** in the default (lean) stack — a **functional hub**: PNH plus the private relayer and proofs-api, so PN↔PNH messaging and Enygma work. Without it, **hub-less is the default**: no PNH, no private relayer, no proofs API — the privacy nodes intercommunicate via the public chain only. Hub-less currently applies to `--local` stacks only: it needs the 3.0.1 `rayls-sovereign-contracts` / `rayls-sovereign-relayer` sources (on `main`), which the published ECR images predate, so pulled-image inits keep the hub until the images catch up. Every `--local` init records its build sources in the stack `.env` — the local sibling checkouts (`../rayls-sovereign-contracts`, `../rayls-sovereign-relayer`, recorded as relative paths) via `CONTRACTS_SRC`/`RELAYER_SRC` when present (whatever branch is checked out there is what builds), else the `main` git contexts via `CONTRACTS_REF`/`RELAYER_REF`. `--full` always includes the full hub. * `--members `: Number of privacy node participants. With `--full`: 2–6 (default **2**). For the hub-less default topology: 1–6 (default **1**) — the nodes intercommunicate via the public chain, so any count is meaningful. Ignored on hub-carrying lean stacks (`--with-hub` runs a single participant; use `--full` for the multi-participant hub). * `--public-chain `: Public chain preset to bridge to — `local` (an Axyl public chain running **inside the stack**: service `public-chain`, RPC `localhost:8845`, chain id `7331`, deployer genesis-funded, no external connectivity) or `rayls-testnet` (the external testnet). Applied by default for the default (lean) stack — in the hub-less default the public chain is the privacy nodes' only interconnection path: **`local` with `--local`, `rayls-testnet` otherwise**. `--full --local` also defaults to `local` (the 3.0.1 source deploy requires a public chain); only `--full` with pulled images runs without one. Adds per-participant `pubrelayer` services. * `--privacy-node-only`: Run just a single Axyl privacy node, with no bridge or surrounding services. Ignores all other flags. @@ -193,7 +193,7 @@ PRIVATE_KEY_SYSTEM=<0x-hex> ./rayls init --full #### 3. Hub-less (the default topology) -The default topology runs the environment **without the Private Network Hub**, mirroring `start_dev.sh --no-hub` in the `rayls-privacy-relayer-api` repo: no `private-network-hub` (Besu), no private relayers, no proofs API, no governance and no audit explorer (those belong to the hub topologies — use `--with-hub` or `--full` for them). The privacy nodes intercommunicate through the **public chain only**, so a public chain is always configured — with `--local` it defaults to the `local` preset (an Axyl public chain inside the stack), making the whole system **fully self-contained on one host**; pass `--public-chain rayls-testnet` to bridge to the external testnet instead. The contracts deploy runs with `HUB_ENABLED=false`, writes no `PNH_*` values into the per-participant env files, and the CTS detects hub-less mode from their absence. +The default topology runs the environment **without the Private Network Hub**, mirroring `start_dev.sh --no-hub` in the `rayls-sovereign-relayer` repo: no `private-network-hub` (Besu), no private relayers, no proofs API, no governance and no audit explorer (those belong to the hub topologies — use `--with-hub` or `--full` for them). The privacy nodes intercommunicate through the **public chain only**, so a public chain is always configured — with `--local` it defaults to the `local` preset (an Axyl public chain inside the stack), making the whole system **fully self-contained on one host**; pass `--public-chain rayls-testnet` to bridge to the external testnet instead. The contracts deploy runs with `HUB_ENABLED=false`, writes no `PNH_*` values into the per-participant env files, and the CTS detects hub-less mode from their absence. ```bash ./rayls init --local # single PN <-> local public chain, fully isolated @@ -201,7 +201,7 @@ The default topology runs the environment **without the Private Network Hub**, m ./rayls init --local --public-chain rayls-testnet # hub-less, bridged to the testnet ``` -> Hub-less needs the `HUB_ENABLED`-aware contracts deploy and the hub-less-capable CTS, which ship with `version/3.0.1` of `rayls-privacy-contracts` / `rayls-privacy-relayer-api`. The published ECR images predate that, so hub-less currently applies to `--local` inits only (pulled-image inits keep the minimal hub); the CLI records the build sources in the stack `.env` — preferring the local sibling checkouts (`CONTRACTS_SRC`/`RELAYER_SRC`, so in-flight hub-less branches build as checked out) and falling back to the `version/3.0.1` git contexts (`CONTRACTS_REF`/`RELAYER_REF`). Override either in `.env` if needed. +> Hub-less needs the `HUB_ENABLED`-aware contracts deploy and the hub-less-capable CTS, which ship in the 3.0.1 `rayls-sovereign-contracts` / `rayls-sovereign-relayer` repos (on `main`). The published ECR images predate that, so hub-less currently applies to `--local` inits only (pulled-image inits keep the minimal hub); the CLI records the build sources in the stack `.env` — preferring the local sibling checkouts (`CONTRACTS_SRC`/`RELAYER_SRC`, so in-flight hub-less branches build as checked out) and falling back to the `main` git contexts (`CONTRACTS_REF`/`RELAYER_REF`). Override either in `.env` if needed. #### 4. Privacy node only @@ -359,36 +359,40 @@ A bare `rayls init` pulls the published `rayls-demo` images from ECR. `rayls ini | Image group | default `rayls init` (remote) | `rayls init --local` (from source) | |---|---|---| -| kos, pubrelayer, backend, contracts | published ECR `rayls-demo` images | **built by Docker** from the pinned git refs, or your checkouts via `rayls dev` | +| kos, pubrelayer, private relayer, contracts | published ECR `rayls-demo` images | **built by Docker** from the pinned git refs (the `rayls-sovereign-*` repos on `main`), or your checkouts via `rayls dev` | +| `--full` extras: governance-api/listener/flagger, proofs-api (gnark), audit-explorer | ECR | **built by Docker** from their `rayls-sovereign-*` repos on `main` (see note on gnark's Git-LFS keys below) | | Axyl privacy node | pulled from ECR | pulled from ECR + retagged (a local `rayls-privacy-axyl:latest` you built yourself is left alone) | -| mongodb, nats | ECR | ECR (pulled — infra images aren't source-built) | +| nats, Private Network Hub (Besu) | ECR | ECR (pulled — infra images aren't source-built) | | Third-party (postgres, mongo-express, blockscout, nginx, grafana) | public registries | public registries | -| `--full` extras (Private Network Hub, governance-api/listener/flagger, audit-explorer, proofs-api, private relayer) | ECR | ⚠️ **not source-buildable yet** — they fall back to local short-name images and must already be present | The published `rayls-demo` service images are **multi-arch (amd64 + arm64)**, so remote `rayls init` runs natively on both Intel/AMD and Apple Silicon. Use `--local` when you want to build from a specific ref or hack on a component — not as an architecture workaround. +> ⚠️ **gnark (`proofs-api`) uses Git-LFS.** Its proving keys are Git-LFS blobs, which a pinned **git-context** build can't fetch, so the default `--local` build of `proofs-api` would ship pointer files. For a working Enygma stack, run `rayls dev gnark` (clones the repo and runs `git lfs pull`) or use the pulled ECR image. The hub-less default stack never builds `proofs-api`, so this only matters for `--with-hub` / `--full`. +> +> The `rayls-sovereign-ops-api` service (the former backend) is intentionally **not** wired into the CLI. + ## Choosing a version -In `--local` mode every app component builds from a pinned git ref. One variable in `.env` (or the environment) selects the whole set once the repos tag coordinated releases: +In `--local` mode every app component builds from a pinned git ref. By default that ref is **`main`** of each component's public `rayls-sovereign-*` repo — the 3.0.1 code, which the repos currently carry as a single `main` branch (no version tags yet). Once the repos tag coordinated releases, one variable in `.env` (or the environment) selects the whole set: ```bash -RAYLS_VERSION=3.0.0 # all components build from tag v3.0.0 +RAYLS_VERSION=3.0.2 # all components build from tag v3.0.2 (once such tags exist) ``` Any single component can deviate — point it at a tag, branch, or your own fork: ```bash -BACKEND_REF=fix/reorg-handling # branch on the canonical repo -RELAYER_REF=v3.0.0 # a specific tag -BACKEND_REPO=git@github.com:you/rayls-privacy-backend.git # your fork -BACKEND_SRC=../rayls-privacy-backend # a local checkout (what `rayls dev` sets) +RELAYER_REF=fix/reorg-handling # branch on the canonical repo +CONTRACTS_REF=main # a specific ref +RELAYER_REPO=git@github.com:you/rayls-sovereign-relayer.git # your fork +CONTRACTS_SRC=../rayls-sovereign-contracts # a local checkout (what `rayls dev` sets) ``` Re-run `rayls init --local` after changing `RAYLS_VERSION` / `*_REF` / `*_REPO` (they're baked into the generated compose file); `*_SRC` changes apply on the next `docker compose up` without regenerating. ### Configuration reference (`.env`) -The stack directory's `.env` file drives both the CLI and compose; the process environment always wins over the file. See [.env.example](.env.example) for a commented template. The component prefixes are `CONTRACTS`, `RELAYER` (kos + pubrelayer), and `BACKEND`. +The stack directory's `.env` file drives both the CLI and compose; the process environment always wins over the file. See [.env.example](.env.example) for a commented template. The component prefixes are `CONTRACTS`, `RELAYER` (kos + pubrelayer + private relayer), `GOVERNANCE`, `GNARK`, and `AUDITOR`. | Variable | Purpose | Applies | |---|---|---| @@ -400,24 +404,26 @@ The stack directory's `.env` file drives both the CLI and compose; the process e | `RAYLS_SRC_DIR` | Where `rayls dev` looks for / clones checkouts (default: parent of the stack dir) | `rayls dev` | | `RAYLS_AXYL_IMAGE` | Image to pull + retag as `rayls-privacy-axyl:latest` in `--local` | next `init --local` | -> **Default refs** (until the component repos tag coordinated `v` releases in lockstep): contracts → `lean-no-pnh-3.0.0`, relayer & backend → `v3.0.0`. Set `RAYLS_VERSION` or a per-component `*_REF` to override. +> **Default refs** (until the component repos tag coordinated `v` releases in lockstep): every component defaults to `main` — the `rayls-sovereign-*` repos' 3.0.1 code. Set `RAYLS_VERSION` or a per-component `*_REF` to override. ## Working on a component (hot reload) `rayls dev ` switches a component from its pinned build to a local checkout you can edit, with hot reload via `rayls watch`. See **[DEV_MODE.md](DEV_MODE.md)** for the full guide. In short: ```bash -./rayls dev backend # clone (if needed) + switch backend to a local checkout you edit +./rayls dev relayer # clone (if needed) + switch relayer to a local checkout you edit ./rayls watch # sync saves into the container; air rebuilds & restarts in ~seconds ./rayls dev --status # what's in dev mode? -./rayls dev --off backend # back to the pinned build +./rayls dev --off relayer # back to the pinned build ``` | Component | Repo | Services | Hot reload | | --- | --- | --- | --- | -| `relayer` | rayls-privacy-relayer-api | `kos-*`, `pubrelayer-*` | yes | -| `backend` | rayls-privacy-backend | `backend-*` | yes | -| `contracts` | rayls-privacy-contracts | `contracts` | no — builds from your checkout, but deploys are explicit: re-run `rayls init --local` (or restart the contracts service) after contract changes | +| `relayer` | rayls-sovereign-relayer | `kos-*`, `pubrelayer-*`, `relayer-*` | yes | +| `governance` | rayls-sovereign-pnh-governance | `governance-api`, `governance-listener`, `governance-flagger` | no — build from checkout, rebuild to apply (`--full` only) | +| `gnark` | rayls-sovereign-gnark-api | `proofs-api` | no — Git-LFS keys; use `rayls dev gnark` (LFS-aware) or the pulled image | +| `auditor` | rayls-sovereign-pnh-auditor-ui | `audit-explorer` | no — Angular→nginx build from checkout (`--full` only) | +| `contracts` | rayls-sovereign-contracts | `contracts` | no — builds from your checkout, but deploys are explicit: re-run `rayls init --local` (or restart the contracts service) after contract changes | | node | axyl | `privacy-node-*` | no — pulled as a published image; build it yourself for node work | ## Architecture diff --git a/cmd/dev.go b/cmd/dev.go index 6201ccd..2b60e76 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -36,7 +36,7 @@ exception — they build from your checkout but redeploys stay explicit. Examples: rayls dev relayer # hack on the relayer (kos + pubrelayer) rayls dev relayer contracts # both at once - rayls dev relayer --repo git@github.com:you/rayls-privacy-relayer-api.git + rayls dev relayer --repo git@github.com:you/rayls-sovereign-relayer.git rayls dev --status # what's in dev mode? rayls dev --off relayer # back to the pinned build`, Run: func(cmd *cobra.Command, args []string) { diff --git a/cmd/init.go b/cmd/init.go index 0d89918..a18a423 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -71,7 +71,7 @@ the public chain only. --with-hub adds the minimal Private Network Hub; // via the public chain only. --with-hub opts the lean stack into the // minimal Private Network Hub; --full always brings the complete hub // stack. Hub-less needs the HUB_ENABLED-aware contracts deploy and the - // hub-less-capable CTS (>= version/3.0.1 of their repos), which the + // hub-less-capable CTS (the rayls-sovereign-* 3.0.1 sources on main), which the // published ECR images predate — pulled-image stacks therefore keep the // minimal hub until the images are republished (same conditional default // as the public-chain preset below; documented in --help and README). diff --git a/internal/docker/docker_config.go b/internal/docker/docker_config.go index a00f9fb..f5db154 100644 --- a/internal/docker/docker_config.go +++ b/internal/docker/docker_config.go @@ -161,7 +161,16 @@ func localImage(image string, local bool) (string, string) { return image, "" } shortName := strings.TrimPrefix(image, raylsECRPrefix) - for _, buildable := range []string{"rayls-kos", "rayls-pubrelayer", "rayls-contracts", "rayls-privacy-axyl"} { + // Every image here must have a matching attachBuild call in --local, or the + // short-name + pull_policy=never below leaves compose with "No such image". + // kos/pubrelayer/relayer come from the relayer component; governance trio + // from the governance component; proofs-api from gnark; audit-explorer from + // auditor; contracts from contracts; axyl is retagged by ensureAxylImage. + for _, buildable := range []string{ + "rayls-kos", "rayls-pubrelayer", "rayls-relayer", "rayls-contracts", "rayls-privacy-axyl", + "rayls-governance-api", "rayls-governance-listener", "rayls-governance-flagger", + "rayls-proof-api", "rayls-audit-explorer", + } { if strings.HasPrefix(shortName, buildable) { return shortName, "never" } @@ -462,7 +471,7 @@ func getKosServices(participants []string, monitoring bool, local bool, lean boo return services } -func getRelayerServices(participants []string, monitoring bool, local bool) map[string]*Service { +func getRelayerServices(participants []string, monitoring bool, local bool, srcs *Sources) map[string]*Service { services := make(map[string]*Service) otelSdkDisabled := "true" otelEndpoint := "" @@ -529,6 +538,11 @@ func getRelayerServices(participants []string, monitoring bool, local bool) map[ Environment: env, } } + // From-source build (--local): attach to the first participant's service + // only; siblings reuse the tag the build produces (same pattern as kos). + if len(participants) > 0 { + attachBuild(services["relayer-"+participants[0]], srcs, "relayer", "relayer") + } return services } @@ -898,12 +912,13 @@ func getContractsService(participants []string, local bool, pc *PublicChain, lea } // PULLED lean-with-hub mode uses a dedicated image tag that carries the - // PNH_ENABLED-aware deploy (built from rayls-privacy-contracts branch - // cli-lean-no-pnh). This keeps :latest — the non-lean default — untouched. - // Every --local stack (lean hub-less, lean with-hub, full) instead builds - // the mainline HUB_ENABLED-aware deploy from the pinned >= version/3.0.1 - // sources (see the stack .env pins) and tags it :latest — one image serves - // all local topologies, HUB_ENABLED switching the deploy path. + // PNH_ENABLED-aware deploy (built from the old rayls-privacy-contracts + // branch cli-lean-no-pnh). This keeps :latest — the non-lean default — + // untouched. Every --local stack (lean hub-less, lean with-hub, full) + // instead builds the mainline HUB_ENABLED-aware deploy from the pinned + // rayls-sovereign-contracts main sources (see the stack .env pins) and + // tags it :latest — one image serves all local topologies, HUB_ENABLED + // switching the deploy path. contractsRef := "public.ecr.aws/w0k9o1t3/rayls-demo/rayls-contracts:latest" if lean && !noHub && !local { contractsRef = "public.ecr.aws/w0k9o1t3/rayls-demo/rayls-contracts:lean-no-pnh" @@ -932,7 +947,7 @@ func getContractsService(participants []string, local bool, pc *PublicChain, lea // From-source build. attachBuild no-ops when srcs is nil (every non---local // mode), so this covers exactly the --local stacks — lean (with or without // hub) and full alike, all building the HUB_ENABLED-aware deploy from the - // pinned >= version/3.0.1 sources. Previously full+--local skipped the + // pinned rayls-sovereign-contracts main sources. Previously full+--local skipped the // attach and silently depended on a rayls-contracts:latest image left // behind by an earlier lean/hub-less build. attachBuild(svc, srcs, "contracts", "contracts") @@ -1214,6 +1229,18 @@ func GetDemoComposeConfig(participants []string, monitoring bool, blockscout []s }, } + // From-source builds (--local) for the components defined inline above. + // attachBuild no-ops when srcs is nil (pulled mode); it flips the image to + // the short-name local build (see localImage's whitelist) + pull_policy=build. + // These services may be trimmed later by applyLeanNoPNH/applyNoHub, and the + // build section goes with them: proofs-api survives lean-with-hub; the + // governance trio and audit-explorer are --full only. + attachBuild(compose.Services["governance-api"], srcs, "governance", "governance-api") + attachBuild(compose.Services["governance-listener"], srcs, "governance", "governance-listener") + attachBuild(compose.Services["governance-flagger"], srcs, "governance", "governance-flagger") + attachBuild(compose.Services["proofs-api"], srcs, "gnark", "proofs-api") + attachBuild(compose.Services["audit-explorer"], srcs, "auditor", "audit-explorer") + if monitoring { compose.Services["otel"] = &Service{ Image: "docker.io/grafana/otel-lgtm:latest", @@ -1250,7 +1277,7 @@ func GetDemoComposeConfig(participants []string, monitoring bool, blockscout []s compose.Services[name] = service } - relayerServices := getRelayerServices(participants, monitoring, local) + relayerServices := getRelayerServices(participants, monitoring, local, srcs) for name, service := range relayerServices { compose.Services[name] = service } @@ -1313,8 +1340,8 @@ func GetDemoComposeConfig(participants []string, monitoring bool, blockscout []s // services are rewritten so they no longer wait on removed ones. // // The topology is deliberately NOT forked on image provenance: the published -// ECR images still predate the version/3.0.1 components this shape needs (the -// private relayer's config contract in particular), but they are being +// ECR images still predate the 3.0.1 (rayls-sovereign-*) components this shape +// needs (the private relayer's config contract in particular), but they are being // republished from the current codebases — the shape targets that, not // today's registry state. func applyLeanNoPNH(compose *DockerCompose, participants []string) { @@ -1357,9 +1384,9 @@ func applyLeanNoPNH(compose *DockerCompose, participants []string) { // The runtime hub-less switch is downstream of the contracts deploy: with // HUB_ENABLED=false (set in getContractsService) the deploy writes no PNH_* // vars into the per-participant .X.env files, and the 3.0.1 CTS keys hub-less -// mode off the absence of PNH_DEPLOYMENT_PROXY_REGISTRY. Requires contracts + -// relayer-api components >= version/3.0.1 (see GenerateDockerCompose's source -// pinning). +// mode off the absence of PNH_DEPLOYMENT_PROXY_REGISTRY. Requires the 3.0.1 +// contracts + relayer components (rayls-sovereign-* on main; see +// GenerateDockerCompose's source pinning). func applyNoHub(compose *DockerCompose, participants []string) { drop := []string{ "private-network-hub", diff --git a/internal/docker/docker_config_local_test.go b/internal/docker/docker_config_local_test.go index ffc485c..60db88e 100644 --- a/internal/docker/docker_config_local_test.go +++ b/internal/docker/docker_config_local_test.go @@ -28,19 +28,33 @@ func TestGetDemoComposeConfigLocal(t *testing.T) { if strings.Contains(s, "image: public.ecr.aws/w0k9o1t3/rayls-demo/rayls-kos:latest") { t.Errorf("kos should not reference ECR when local=true") } - // Images without a source-build path are never short-named — full+--local - // died with "No such image: rayls-proof-api:latest" when they were. + // Images without a source-build path are never short-named — they keep the + // full ECR ref so `up` can pull them (full+--local died with "No such + // image" when a non-buildable image was short-named). for _, ecrImage := range []string{ "rayls-nats:latest", "rayls-private-network-hub:latest", - "rayls-proof-api:latest", + } { + if !strings.Contains(s, "image: public.ecr.aws/w0k9o1t3/rayls-demo/"+ecrImage) { + t.Errorf("%s should stay on ECR in --local", ecrImage) + } + } + // Source-buildable images ARE short-named in --local (a local build/tag + // provides them): the governance trio, proofs-api, audit-explorer and the + // private relayer, alongside kos/pubrelayer/contracts. + for _, shortImage := range []string{ + "rayls-relayer:latest", "rayls-governance-api:latest", "rayls-governance-listener:latest", "rayls-governance-flagger:latest", + "rayls-proof-api:latest", "rayls-audit-explorer:latest", } { - if !strings.Contains(s, "image: public.ecr.aws/w0k9o1t3/rayls-demo/"+ecrImage) { - t.Errorf("%s should stay on ECR in --local", ecrImage) + if !strings.Contains(s, "image: "+shortImage) { + t.Errorf("%s should be short-named (source-buildable) in --local", shortImage) + } + if strings.Contains(s, "image: public.ecr.aws/w0k9o1t3/rayls-demo/"+shortImage) { + t.Errorf("%s should not reference ECR when local=true", shortImage) } } } @@ -62,15 +76,17 @@ func TestGetDemoComposeConfigLocalFromSource(t *testing.T) { if kosA.Build == nil { t.Fatalf("kos-a should have a build section") } - wantCtx := "${RELAYER_SRC:-git@github.com:raylsnetwork/rayls-privacy-relayer-api.git#v3.0.0}" + wantCtx := "${RELAYER_SRC:-git@github.com:raylsnetwork/rayls-sovereign-relayer.git#main}" if kosA.Build.Context != wantCtx { t.Errorf("kos-a context = %q, want %q", kosA.Build.Context, wantCtx) } if kosA.Build.Dockerfile != "cts/Dockerfile" { t.Errorf("kos-a dockerfile = %q, want cts/Dockerfile (production)", kosA.Build.Dockerfile) } + // The sovereign repos are currently private git@ URLs, so the build forwards + // the ssh agent. (Flip to len==0 once they go public and the URLs become https.) if len(kosA.Build.Ssh) != 1 || kosA.Build.Ssh[0] != "default" { - t.Errorf("kos-a should forward the ssh agent for the private git context, got %v", kosA.Build.Ssh) + t.Errorf("kos-a should forward the ssh agent for the private git@ context, got %v", kosA.Build.Ssh) } if kosA.PullPolicy != "build" { t.Errorf("kos-a pull_policy = %q, want build", kosA.PullPolicy) @@ -89,8 +105,8 @@ func TestGetDemoComposeConfigLocalFromSource(t *testing.T) { t.Errorf("%s dockerfile = %q, want %q", service, svc.Build.Dockerfile, dockerfile) } } - if got := compose.Services["contracts"].Build.Context; !strings.Contains(got, "#lean-no-pnh-3.0.0}") { - t.Errorf("contracts context template should default to the registry's lean-no-pnh-3.0.0 ref (the .env pins override it at resolution time), got %q", got) + if got := compose.Services["contracts"].Build.Context; !strings.Contains(got, "#main}") { + t.Errorf("contracts context template should default to the sovereign-contracts main ref (the .env pins override it at resolution time), got %q", got) } // Infra images stay pulled from ECR. @@ -107,3 +123,54 @@ func TestGetDemoComposeConfigLocalFromSource(t *testing.T) { t.Errorf("privacy-node-a should not build from source") } } + +// --local --full source-builds the remaining components too: the private +// relayer (from the relayer repo), the governance trio, proofs-api (gnark) and +// the audit explorer — each from its rayls-sovereign-* repo on main. +func TestGetDemoComposeConfigFullLocalSourceBuilds(t *testing.T) { + t.Chdir(t.TempDir()) // isolate from any real .env + srcs, err := ResolveSources() + if err != nil { + t.Fatalf("ResolveSources: %v", err) + } + pc := PublicChainPresets["rayls-testnet"] + // full (lean=false), with hub (noHub=false), --local, from source. + compose := GetDemoComposeConfig([]string{"a", "b"}, false, nil, true, &pc, false, false, srcs) + + cases := map[string]struct{ dockerfile, repo string }{ + "relayer-a": {"private-relayer/Dockerfile", "rayls-sovereign-relayer"}, + "governance-api": {"Dockerfile.api", "rayls-sovereign-pnh-governance"}, + "governance-listener": {"Dockerfile.listener", "rayls-sovereign-pnh-governance"}, + "governance-flagger": {"Dockerfile.flagger", "rayls-sovereign-pnh-governance"}, + "proofs-api": {"Dockerfile", "rayls-sovereign-gnark-api"}, + "audit-explorer": {"Dockerfile", "rayls-sovereign-pnh-auditor-ui"}, + } + for svcName, want := range cases { + svc := compose.Services[svcName] + if svc == nil { + t.Errorf("%s missing in --local --full", svcName) + continue + } + if svc.Build == nil { + t.Errorf("%s should build from source in --local --full", svcName) + continue + } + if svc.Build.Dockerfile != want.dockerfile { + t.Errorf("%s dockerfile = %q, want %q", svcName, svc.Build.Dockerfile, want.dockerfile) + } + if !strings.Contains(svc.Build.Context, want.repo) || !strings.Contains(svc.Build.Context, "#main}") { + t.Errorf("%s context = %q, want it to reference %s on main", svcName, svc.Build.Context, want.repo) + } + if svc.PullPolicy != "build" { + t.Errorf("%s pull_policy = %q, want build", svcName, svc.PullPolicy) + } + if strings.HasPrefix(svc.Image, "public.ecr.aws/") { + t.Errorf("%s should be short-named when source-built, got %q", svcName, svc.Image) + } + } + // Only the first participant's private relayer carries the build; siblings + // reuse the produced tag. + if b := compose.Services["relayer-b"].Build; b != nil { + t.Errorf("relayer-b should reuse relayer-a's build, not carry its own: %+v", b) + } +} diff --git a/internal/docker/docker_config_nohub_test.go b/internal/docker/docker_config_nohub_test.go index 7eb57f6..83c8b1b 100644 --- a/internal/docker/docker_config_nohub_test.go +++ b/internal/docker/docker_config_nohub_test.go @@ -163,7 +163,7 @@ func TestGetDemoComposeConfigNoHubLean(t *testing.T) { func TestGetDemoComposeConfigNoHubLocalFromSource(t *testing.T) { t.Chdir(t.TempDir()) // Simulate the pin GenerateDockerCompose writes for hub-less --local stacks. - if err := os.WriteFile(".env", []byte("CONTRACTS_REF=version/3.0.1\nRELAYER_REF=version/3.0.1\n"), 0644); err != nil { + if err := os.WriteFile(".env", []byte("CONTRACTS_REF=main\nRELAYER_REF=main\n"), 0644); err != nil { t.Fatal(err) } srcs, err := ResolveSources() @@ -180,14 +180,14 @@ func TestGetDemoComposeConfigNoHubLocalFromSource(t *testing.T) { if contracts.Build.Dockerfile != "Dockerfile.dev" { t.Errorf("contracts dockerfile = %q, want Dockerfile.dev", contracts.Build.Dockerfile) } - if want := "${CONTRACTS_SRC:-git@github.com:raylsnetwork/rayls-privacy-contracts.git#version/3.0.1}"; contracts.Build.Context != want { + if want := "${CONTRACTS_SRC:-git@github.com:raylsnetwork/rayls-sovereign-contracts.git#main}"; contracts.Build.Context != want { t.Errorf("contracts context = %q, want %q", contracts.Build.Context, want) } kosA := compose.Services["kos-a"] if kosA.Build == nil { t.Fatalf("kos-a should have a build section in --local") } - if want := "${RELAYER_SRC:-git@github.com:raylsnetwork/rayls-privacy-relayer-api.git#version/3.0.1}"; kosA.Build.Context != want { + if want := "${RELAYER_SRC:-git@github.com:raylsnetwork/rayls-sovereign-relayer.git#main}"; kosA.Build.Context != want { t.Errorf("kos-a context = %q, want %q", kosA.Build.Context, want) } } diff --git a/internal/docker/docker_config_pull_test.go b/internal/docker/docker_config_pull_test.go index 19c97a5..d0eb958 100644 --- a/internal/docker/docker_config_pull_test.go +++ b/internal/docker/docker_config_pull_test.go @@ -41,11 +41,12 @@ func TestLocalStackStillHasRegistryImages(t *testing.T) { } // ECR images --local deliberately keeps pulling (no source-build path). + // Only the genuinely non-source-built infra remains: NATS and the Besu + // private-network-hub. The relayer, proofs-api (gnark), governance trio and + // audit-explorer are now source-built (see the Components registry). for _, image := range []string{ raylsECRPrefix + "rayls-nats:latest", raylsECRPrefix + "rayls-private-network-hub:latest", - raylsECRPrefix + "rayls-relayer:latest", - raylsECRPrefix + "rayls-proof-api:latest", } { if !pulled[image] { t.Errorf("%s should still be pulled in --local mode", image) @@ -53,7 +54,12 @@ func TestLocalStackStillHasRegistryImages(t *testing.T) { } // Source-built components must not be in the pull set. for image := range pulled { - for _, short := range []string{"rayls-kos:", "rayls-pubrelayer:", "rayls-contracts:", "rayls-privacy-axyl:"} { + for _, short := range []string{ + "rayls-kos:", "rayls-pubrelayer:", "rayls-relayer:", "rayls-contracts:", + "rayls-privacy-axyl:", "rayls-proof-api:", + "rayls-governance-api:", "rayls-governance-listener:", "rayls-governance-flagger:", + "rayls-audit-explorer:", + } { if strings.HasPrefix(image, short) { t.Errorf("%s should be built/never-pulled in --local mode, not pulled", image) } diff --git a/internal/docker/sources.go b/internal/docker/sources.go index 0c6fdab..dd86e72 100644 --- a/internal/docker/sources.go +++ b/internal/docker/sources.go @@ -22,12 +22,20 @@ type ServiceBuild struct { // A single component may produce several images (the relayer-api repo builds // both the CTS/kos and the public-relayer). type Component struct { - Key string // CLI-facing name used by `rayls dev` (e.g. "relayer") - EnvPrefix string // prefix for the _SRC/_REF/_REPO override vars (e.g. "RELAYER") - Repo string // default git URL - DirName string // directory name used for local checkouts - DefaultRef string // ref the 3.0.0 stack images are built from - Watch bool // participates in hot reload (`rayls dev` + `docker compose watch`) + Key string // CLI-facing name used by `rayls dev` (e.g. "relayer") + EnvPrefix string // prefix for the _SRC/_REF/_REPO override vars (e.g. "RELAYER") + Repo string // default git URL + DirName string // directory name used for local checkouts + // GlossaryPath is the /parfin/ directory the contracts deploy + // writes this component's per-participant env files to and the service + // reads via --env / ENV_FILE. It is the container-side config path and is + // deliberately decoupled from the source repo/checkout name (DirName): the + // GitHub repos were renamed to rayls-sovereign-*, but the deploy still + // writes the historical rayls-privacy-* glossary paths. Empty for + // components that have no per-participant env files (e.g. contracts). + GlossaryPath string + DefaultRef string // git ref to build from (the sovereign repos' main == 3.0.1 code) + Watch bool // participates in hot reload (`rayls dev` + `docker compose watch`) // Services maps compose service-name prefixes built from this repo to // their build configuration. @@ -35,35 +43,94 @@ type Component struct { } // Components is the registry of Rayls components buildable from source in -// --local mode, pinned to the same refs the published 3.0.0 ECR images were -// built from. Infra images (nats, private-network-hub, the private relayer, -// proofs-api, axyl) are not source-built — see localImage/ensureAxylImage. +// --local mode. Each points at its GitHub repo on `main`: the rayls-sovereign-* +// repos hold the 3.0.1 code copied over as a single `main` branch (no version +// tags yet), so `main` is the build ref. +// +// TODO(sovereign-public): the rayls-sovereign-* repos are currently PRIVATE, so +// the default context uses SSH (git@...). BuildSection adds build.ssh:[default] +// for git@/ssh:// URLs, so `docker compose up --build` forwards your ssh-agent +// — run `ssh-add -l` and make sure a key with raylsnetwork access is loaded. +// Once the admin makes the repos public, switch every Repo below back to +// https://github.com/raylsnetwork/.git (anonymous clone, no ssh-agent); +// BuildSection then drops the ssh forwarding automatically. See also the +// matching notes in README.md / DEV_MODE.md. +// +// Infra images (nats, private-network-hub, axyl) are not source-built — see +// localImage/ensureAxylImage. var Components = []Component{ { Key: "contracts", EnvPrefix: "CONTRACTS", - Repo: "git@github.com:raylsnetwork/rayls-privacy-contracts.git", - DirName: "rayls-privacy-contracts", - DefaultRef: "lean-no-pnh-3.0.0", + Repo: "git@github.com:raylsnetwork/rayls-sovereign-contracts.git", + DirName: "rayls-sovereign-contracts", + DefaultRef: "main", Watch: false, // contract changes need an explicit redeploy, not a file watcher Services: map[string]ServiceBuild{ - // Dockerfile.dev is what the published lean-no-pnh image is built - // from (the deploy tooling image; "dev" is historical naming). + // Dockerfile.dev is the deploy-tooling image (the "dev" naming is + // historical). On sovereign-contracts main it bakes the /parfin env + // templates and honors HUB_ENABLED/OPS_API_ENABLED/GOVERNANCE_ENABLED. "contracts": {Dockerfile: "Dockerfile.dev"}, }, }, { - Key: "relayer", - EnvPrefix: "RELAYER", - Repo: "git@github.com:raylsnetwork/rayls-privacy-relayer-api.git", - DirName: "rayls-privacy-relayer-api", - // v3.0.0 is an immutable tag (== version/3.0.0 head); version/3.0.1 - // is still in development. Override per stack via RELAYER_REF. - DefaultRef: "v3.0.0", + Key: "relayer", + EnvPrefix: "RELAYER", + Repo: "git@github.com:raylsnetwork/rayls-sovereign-relayer.git", + DirName: "rayls-sovereign-relayer", + GlossaryPath: relayerPathV3, // deploy still writes /parfin/rayls-privacy-relayer-api + // The sovereign repos carry only a single `main` branch (the copied-over + // 3.0.1 code); no version tags exist yet. Override per stack via + // RELAYER_REF once tags land. + DefaultRef: "main", Watch: true, Services: map[string]ServiceBuild{ "kos": {Dockerfile: "cts/Dockerfile", DevDockerfile: "cts/Dockerfile.dev", AirConfig: "cts/air.toml"}, "pubrelayer": {Dockerfile: "public-relayer/Dockerfile", DevDockerfile: "public-relayer/Dockerfile.dev", AirConfig: "public-relayer/air.toml"}, + // The private relayer (compose service relayer-

) now lives in this + // same repo under private-relayer/ (it was the repo root pre-3.0.1). + "relayer": {Dockerfile: "private-relayer/Dockerfile", DevDockerfile: "private-relayer/Dockerfile.dev", AirConfig: "private-relayer/air.toml"}, + }, + }, + { + Key: "governance", + EnvPrefix: "GOVERNANCE", + Repo: "git@github.com:raylsnetwork/rayls-sovereign-pnh-governance.git", + DirName: "rayls-sovereign-pnh-governance", + GlossaryPath: governancePathV3, // deploy writes /parfin/rayls-privacy-pnh-governance-api + DefaultRef: "main", + Watch: false, // no air wiring here; governance changes need an explicit rebuild + Services: map[string]ServiceBuild{ + "governance-api": {Dockerfile: "Dockerfile.api"}, + "governance-listener": {Dockerfile: "Dockerfile.listener"}, + "governance-flagger": {Dockerfile: "Dockerfile.flagger"}, + }, + }, + { + Key: "gnark", + EnvPrefix: "GNARK", + Repo: "git@github.com:raylsnetwork/rayls-sovereign-gnark-api.git", + DirName: "rayls-sovereign-gnark-api", + DefaultRef: "main", + Watch: false, + Services: map[string]ServiceBuild{ + // gnark's proving/verifying keys under last_build/ are Git-LFS blobs. + // BuildKit's git-context clone does NOT smudge LFS, so a pinned + // git-context build ships pointer files and the server fails to load + // keys. Use `rayls dev gnark` (clones + `git lfs pull`, see + // ensureCheckout) or the pulled ECR image for a working proofs-api. + "proofs-api": {Dockerfile: "Dockerfile"}, + }, + }, + { + Key: "auditor", + EnvPrefix: "AUDITOR", + Repo: "git@github.com:raylsnetwork/rayls-sovereign-pnh-auditor-ui.git", + DirName: "rayls-sovereign-pnh-auditor-ui", + DefaultRef: "main", + Watch: false, // Angular -> nginx; no hot reload + Services: map[string]ServiceBuild{ + "audit-explorer": {Dockerfile: "Dockerfile"}, }, }, } diff --git a/internal/docker/sources_test.go b/internal/docker/sources_test.go index 62be10e..6a81bd1 100644 --- a/internal/docker/sources_test.go +++ b/internal/docker/sources_test.go @@ -25,18 +25,18 @@ func resolveIn(t *testing.T, dotenv string) *Sources { func TestSourcesDefaults(t *testing.T) { srcs := resolveIn(t, "") for key, want := range map[string]string{ - "contracts": "lean-no-pnh-3.0.0", - "relayer": "v3.0.0", + "contracts": "main", + "relayer": "main", } { if got := srcs.Ref(*ComponentByKey(key)); got != want { t.Errorf("%s default ref = %q, want %q", key, got, want) } } c := *ComponentByKey("relayer") - if got := srcs.Repo(c); got != "git@github.com:raylsnetwork/rayls-privacy-relayer-api.git" { + if got := srcs.Repo(c); got != "git@github.com:raylsnetwork/rayls-sovereign-relayer.git" { t.Errorf("default repo = %q", got) } - want := "${RELAYER_SRC:-git@github.com:raylsnetwork/rayls-privacy-relayer-api.git#v3.0.0}" + want := "${RELAYER_SRC:-git@github.com:raylsnetwork/rayls-sovereign-relayer.git#main}" if got := srcs.BuildContext(c); got != want { t.Errorf("context = %q, want %q", got, want) } @@ -76,15 +76,16 @@ func TestSourcesEnvFileAndProcessPrecedence(t *testing.T) { } func TestSourcesHTTPSRepoDropsSSH(t *testing.T) { - t.Setenv("CONTRACTS_REPO", "https://github.com/raylsnetwork/rayls-privacy-contracts.git") + // The sovereign repos are currently private git@ URLs, so the default + // context requests ssh-agent forwarding. An https override (a public fork, + // or once the repos go public) drops it. + t.Setenv("CONTRACTS_REPO", "https://github.com/raylsnetwork/rayls-sovereign-contracts.git") srcs := resolveIn(t, "") - b := srcs.BuildSection("contracts", "contracts") - if len(b.Ssh) != 0 { - t.Errorf("https context should not request ssh forwarding, got %v", b.Ssh) + if b := srcs.BuildSection("contracts", "contracts"); len(b.Ssh) != 0 { + t.Errorf("https override should not request ssh forwarding, got %v", b.Ssh) } - sshBuild := srcs.BuildSection("relayer", "kos") - if len(sshBuild.Ssh) != 1 { - t.Errorf("ssh context should request agent forwarding, got %v", sshBuild.Ssh) + if b := srcs.BuildSection("relayer", "kos"); len(b.Ssh) != 1 { + t.Errorf("default git@ context should request ssh agent forwarding, got %v", b.Ssh) } } @@ -106,8 +107,8 @@ func TestSourcesBuildSectionUsesProductionDockerfile(t *testing.T) { } func TestSourcesSrcFromEnvFile(t *testing.T) { - srcs := resolveIn(t, "CONTRACTS_SRC=../rayls-privacy-contracts\n") - if got := srcs.Src(*ComponentByKey("contracts")); got != "../rayls-privacy-contracts" { + srcs := resolveIn(t, "CONTRACTS_SRC=../rayls-sovereign-contracts\n") + if got := srcs.Src(*ComponentByKey("contracts")); got != "../rayls-sovereign-contracts" { t.Errorf("Src = %q", got) } } diff --git a/internal/stacks/dev.go b/internal/stacks/dev.go index 934f66a..35b92ba 100644 --- a/internal/stacks/dev.go +++ b/internal/stacks/dev.go @@ -106,13 +106,16 @@ func participantIndex(serviceName string) int { // envFileFor returns the per-participant env file the dev (air) images read // via the ENV_FILE variable, matching what the base compose passes to the -// production entrypoints via `--env`. +// production entrypoints via `--env`. It uses the component's GlossaryPath +// (the container-side /parfin config dir), NOT its DirName: the sovereign +// repos were renamed but the deploy still writes the historical +// rayls-privacy-* glossary paths. func envFileFor(c *docker.Component, serviceName string) string { suffix := "A" if i := strings.LastIndex(serviceName, "-"); i >= 0 && i == len(serviceName)-2 { suffix = strings.ToUpper(serviceName[i+1:]) } - return fmt.Sprintf("/parfin/%s/.%s.env", c.DirName, suffix) + return fmt.Sprintf("%s/.%s.env", c.GlossaryPath, suffix) } // debugPortFor mirrors the GO_DEBUG_PORT layout of the base compose (dlv @@ -152,6 +155,13 @@ func ensureCheckout(dir, repo, ref string) error { if err := checkout.Run(); err != nil { return fmt.Errorf("checking out %s in %s: %w", ref, dir, err) } + // gnark stores its proving/verifying keys via Git-LFS; a plain clone leaves + // pointer files unless git-lfs smudged them. Pull explicitly so the checkout + // has real blobs. Best-effort: a no-op for non-LFS repos, and skipped (with + // a note) when git-lfs isn't installed. + if exec.Command("git", "-C", dir, "lfs", "pull").Run() != nil { + fmt.Printf(" (note: `git lfs pull` skipped in %s — install git-lfs if this component ships LFS assets, e.g. gnark)\n", dir) + } return nil } diff --git a/internal/stacks/dev_test.go b/internal/stacks/dev_test.go index 40af3c9..ea96e81 100644 --- a/internal/stacks/dev_test.go +++ b/internal/stacks/dev_test.go @@ -57,7 +57,7 @@ func TestRegenerateDevOverride(t *testing.T) { if err := os.WriteFile("docker-compose.yaml", []byte(testComposeYAML), 0644); err != nil { t.Fatal(err) } - if err := os.WriteFile(".env", []byte("RELAYER_SRC=/home/dev/rayls-privacy-relayer-api\nCONTRACTS_SRC=/home/dev/rayls-privacy-contracts\n"), 0644); err != nil { + if err := os.WriteFile(".env", []byte("RELAYER_SRC=/home/dev/rayls-sovereign-relayer\nCONTRACTS_SRC=/home/dev/rayls-sovereign-contracts\n"), 0644); err != nil { t.Fatal(err) } @@ -105,7 +105,7 @@ func TestRegenerateDevOverride(t *testing.T) { // contracts builds from the checkout but never hot-reloads contracts := serviceSection(s, "contracts") - if !strings.Contains(contracts, `context: "/home/dev/rayls-privacy-contracts"`) { + if !strings.Contains(contracts, `context: "/home/dev/rayls-sovereign-contracts"`) { t.Errorf("contracts should build from the local checkout:\n%s", contracts) } for _, forbidden := range []string{"watch", "command: air", "gocache"} { diff --git a/internal/stacks/nohub_test.go b/internal/stacks/nohub_test.go index e0de512..1d4ef5b 100644 --- a/internal/stacks/nohub_test.go +++ b/internal/stacks/nohub_test.go @@ -15,7 +15,7 @@ import ( // pinLocalSources records the hub-less build sources for a hub-less stack: // the local sibling checkout via _SRC when one exists, else the -// version/3.0.1 ref pin — never clobbering a source the user already chose. +// localSourcesRef (main) ref pin — never clobbering a source the user already chose. // Set-but-empty env vars neutralize any ambient pins on the machine running // the tests AND exercise the guard's file-over-empty-env behavior (a bare // `export CONTRACTS_REF=` must not make pinLocalSources overwrite an explicit @@ -47,7 +47,7 @@ func TestPinLocalSources(t *testing.T) { } // A sibling checkout wins over the ref pin: / with .git. - contractsCheckout := filepath.Join(root, "rayls-privacy-contracts") + contractsCheckout := filepath.Join(root, "rayls-sovereign-contracts") if err := os.MkdirAll(filepath.Join(contractsCheckout, ".git"), 0755); err != nil { t.Fatal(err) } @@ -63,7 +63,7 @@ func TestPinLocalSources(t *testing.T) { } // Recorded RELATIVE (resolved against the stack dir by compose and every // rayls command) so the .env stays portable across machines. - wantSrc := filepath.Join("..", "rayls-privacy-contracts") + wantSrc := filepath.Join("..", "rayls-sovereign-contracts") if vars["CONTRACTS_SRC"] != wantSrc { t.Errorf("sibling present: CONTRACTS_SRC = %q, want %q", vars["CONTRACTS_SRC"], wantSrc) } diff --git a/internal/stacks/publicchainkey_test.go b/internal/stacks/publicchainkey_test.go index 005663b..6c94509 100644 --- a/internal/stacks/publicchainkey_test.go +++ b/internal/stacks/publicchainkey_test.go @@ -251,7 +251,7 @@ func TestPersistPublicChainKey(t *testing.T) { // Pre-existing .env content (build pins) must survive the upsert, and the // file must be tightened to 0600 even though it started 0644. - if err := os.WriteFile(".env", []byte("# pins\nCONTRACTS_REF=version/3.0.1\n"), 0o644); err != nil { + if err := os.WriteFile(".env", []byte("# pins\nCONTRACTS_REF=main\n"), 0o644); err != nil { t.Fatal(err) } @@ -267,7 +267,7 @@ func TestPersistPublicChainKey(t *testing.T) { if !strings.Contains(string(data), publicChainKeyVar+"="+key) { t.Errorf(".env does not contain the persisted key line:\n%s", data) } - if !strings.Contains(string(data), "CONTRACTS_REF=version/3.0.1") { + if !strings.Contains(string(data), "CONTRACTS_REF=main") { t.Errorf("persisting the key clobbered existing .env content:\n%s", data) } info, err := os.Stat(".env") diff --git a/internal/stacks/stack_manager.go b/internal/stacks/stack_manager.go index 919812e..25613c6 100644 --- a/internal/stacks/stack_manager.go +++ b/internal/stacks/stack_manager.go @@ -64,7 +64,8 @@ func GenerateDockerCompose(participants []string, monitoring bool, blockscout [] // Re-record the pins even when keeping the file: the kept // compose still interpolates ${_SRC:-} from .env // on every `up`, and missing pins (e.g. .env deleted to - // reset the key) silently revert builds to 3.0.0-era refs. + // reset the key) silently revert builds to the component + // DefaultRef (rayls-sovereign-* main). if local { if err := pinLocalSources(); err != nil { return false, err @@ -117,12 +118,14 @@ func GenerateDockerCompose(participants []string, monitoring bool, blockscout [] } // localSourcesRef is the ref --local builds contracts/relayer from when no -// sibling checkout exists: DefaultRefs pin the 3.0.0 images' refs, predating the -// HUB_ENABLED deploy and the hub-less CTS. Enough for the relayer; the contracts -// CLI deploy support (baked /parfin env templates, external-PC targeting) is NOT -// on this ref, so a build from the pin fails its deploy — hence the -// sibling-checkout preference and the warning below. -const localSourcesRef = "version/3.0.1" +// sibling checkout exists. The rayls-sovereign-* repos' `main` holds the 3.0.1 +// code: the contracts deploy support (baked /parfin env templates, the +// HUB_ENABLED-gated deploy, the two-key public-chain funding) is all on main, +// so a plain git-context build deploys without a sibling checkout. (One gap +// remains upstream: the lean external-testnet retargeting of +// deploy_public_chain — the default --local stack uses the in-stack `local` +// public chain, which the deploy targets natively, so it is unaffected.) +const localSourcesRef = "main" const localSourcesMarker = "# --local build sources recorded by `rayls init --local` — override or remove these pins to build from different sources." @@ -137,8 +140,11 @@ func pinLocalSources() error { return err } pinned := []string{} - for _, key := range []string{"contracts", "relayer"} { - comp := docker.ComponentByKey(key) + // Pin every buildable component (contracts, relayer, governance, gnark, + // auditor). Components whose services aren't in the current topology (e.g. + // governance in a hub-less stack) get an inert pin — harmless, and correct + // if the stack is later switched to a hub topology. + for _, comp := range docker.Components { srcKey, refKey := comp.EnvPrefix+"_SRC", comp.EnvPrefix+"_REF" if src := envfile.Lookup(fileVars, srcKey); src != "" { if _, statErr := os.Stat(src); statErr != nil { @@ -162,15 +168,12 @@ func pinLocalSources() error { return err } pinned = append(pinned, fmt.Sprintf("%s=%s", refKey, localSourcesRef)) - if key == "contracts" { - fmt.Printf("%s\n", yellow("Warning: no ../rayls-privacy-contracts checkout found — falling back to the remote\nversion/3.0.1 git context, which does not yet include the CLI deploy support.\nClone the contracts repo next to this stack (or set CONTRACTS_SRC in .env)\nuntil that support lands upstream.")) - } } if len(pinned) > 0 { if err := ensureLocalSourcesMarker(); err != nil { return err } - fmt.Printf("%s\n", yellow(fmt.Sprintf("--local: recorded build sources in .env (the registry's 3.0.0-era refs predate the\nHUB_ENABLED-aware deploy and the hub-less CTS). Override there if you want different\nsources.\n %s", strings.Join(pinned, "\n ")))) + fmt.Printf("%s\n", yellow(fmt.Sprintf("--local: recorded build sources in .env (pinned to the rayls-sovereign-* repos on\nmain — the 3.0.1 code). Override there if you want to build from different sources.\n %s", strings.Join(pinned, "\n ")))) } return nil } From bdf5f88260c9f9db82a5cbf8b0e92c5fc3dc763a Mon Sep 17 00:00:00 2001 From: Nuno Date: Fri, 21 Aug 2026 11:07:07 +0100 Subject: [PATCH 2/7] feat: removing foundry linter warnings --- internal/docker/docker_config.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/docker/docker_config.go b/internal/docker/docker_config.go index f5db154..b989509 100644 --- a/internal/docker/docker_config.go +++ b/internal/docker/docker_config.go @@ -753,7 +753,14 @@ func getNatsService(local bool) *Service { // Paths are relative to the image's working dir, and the script avoids `$` and // single quotes — it rides inside a single-quoted compose `command:` that // compose interpolates before the container sees it. -const contractsDeployCommand = `if ls .openzeppelin/*.json >/dev/null 2>&1 && [ -f docker/dev/contracts_deploy_healthcheck.js ]; then ` + +// +// The foundry.toml append switches off forge's lint-on-build (default-on since +// forge 1.x, and the image's foundry.toml has no [lint] section): the image's +// hardhat compile shells out to `forge build`, which otherwise floods every +// deploy step AND every later `docker exec npx hardhat` (rayls verify) with +// ~1500 lint-warning lines per compile, burying the real output. +const contractsDeployCommand = `if [ -f foundry.toml ] && ! grep -q lint_on_build foundry.toml; then printf "\n[lint]\nlint_on_build = false\n" >> foundry.toml; fi; ` + + `if ls .openzeppelin/*.json >/dev/null 2>&1 && [ -f docker/dev/contracts_deploy_healthcheck.js ]; then ` + `echo "[rayls] Contracts are already deployed on this stack (.openzeppelin manifests present)."; ` + `echo "[rayls] Skipping the deploy: the deploy tasks refuse to redeploy over an existing manifest."; ` + `echo "[rayls] Run rayls down -v and then rayls init to wipe the stack volumes and deploy from scratch."; ` + From 089f876ee11692db13d459ddcd22f80a22519f02 Mon Sep 17 00:00:00 2001 From: Nuno Date: Fri, 21 Aug 2026 11:37:45 +0100 Subject: [PATCH 3/7] feat: adding blockscout launch by default. --no-blockscout introduced --- README.md | 44 +++++++++++---------- cmd/init.go | 43 ++++++++++++-------- internal/docker/docker_config.go | 19 +++++++-- internal/docker/docker_config_nohub_test.go | 1 + internal/docker/docker_config_pull_test.go | 6 ++- 5 files changed, 71 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index bd84a3e..ec412a7 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ For a deeper understanding of the Rayls architecture and ecosystem, please visit * **Sequential Image Pulling:** Automatically pulls container images one-by-one — in every mode, `--local` included — to stay under ECR Public's per-IP pull rate limit, cooling off and retrying if a registry throttles anyway. * **Lifecycle Management:** Specialized commands to start, stop, and tear down the stack. * **Monitoring & Observability:** Optional OpenTelemetry stack with eBPF auto-instrumentation, Grafana, Loki, Prometheus, and Tempo. -* **Per-Node Block Explorers:** Optional Blockscout deployment per privacy node. +* **Per-Node Block Explorers:** Blockscout deployment per privacy node (default-on). * **Version Management:** Built-in version checking and update notifications. * **Environment Verification:** Tools to verify the integrity of the setup, including an end-to-end public-chain bridge smoke test. @@ -65,28 +65,28 @@ Download the latest binary for your platform: **macOS (Apple Silicon):** ```bash -curl -L https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-darwin-arm64 -o rayls +curl -fL https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-darwin-arm64 -o rayls chmod +x rayls sudo mv rayls /usr/local/bin/ ``` **macOS (Intel):** ```bash -curl -L https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-darwin-amd64 -o rayls +curl -fL https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-darwin-amd64 -o rayls chmod +x rayls sudo mv rayls /usr/local/bin/ ``` **Linux (x86_64):** ```bash -curl -L https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-linux-amd64 -o rayls +curl -fL https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-linux-amd64 -o rayls chmod +x rayls sudo mv rayls /usr/local/bin/ ``` **Linux (ARM64):** ```bash -curl -L https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-linux-arm64 -o rayls +curl -fL https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-linux-arm64 -o rayls chmod +x rayls sudo mv rayls /usr/local/bin/ ``` @@ -121,7 +121,7 @@ To set up a new environment, run the `init` command. This will: ./rayls init ``` -By default this spins up **Axyl privacy node(s) bridged to a public chain**, with **hub-less as the default topology**: the nodes intercommunicate via the public chain only. With `--local` everything runs on your machine (source builds + an in-stack Axyl public chain). Pulled-image inits (no `--local`) bridge to the Rayls testnet and keep a minimal Private Network Hub for now (the published images predate hub-less support), deploying from **your own funded testnet key**: `rayls init` prompts for it and saves it to the stack `.env` (see [Funding](#funding)). `--with-hub` opts a lean stack into the minimal hub explicitly; `--full` brings the complete hub demo stack. +By default this spins up **Axyl privacy node(s) bridged to a public chain**, with **hub-less as the default topology**: the nodes intercommunicate via the public chain only. With `--local` everything runs on your machine (source builds + an in-stack Axyl public chain). Pulled-image inits (no `--local`) bridge to the Rayls testnet and keep a minimal Private Network Hub for now (the published images predate hub-less support), deploying from **your own funded testnet key**: `rayls init` prompts for it and saves it to the stack `.env` (see [Funding](#funding)). Every participant also gets a Blockscout explorer by default (`--no-blockscout` disables). `--with-hub` opts a lean stack into the minimal hub explicitly; `--full` brings the complete hub demo stack. If a `docker-compose.yaml` already exists, you'll be prompted to overwrite or use the existing file. If you choose to keep the existing file, the CLI will proceed with pulling images and starting containers. @@ -132,15 +132,16 @@ If a `docker-compose.yaml` already exists, you'll be prompted to overwrite or us * `--public-chain `: Public chain preset to bridge to — `local` (an Axyl public chain running **inside the stack**: service `public-chain`, RPC `localhost:8845`, chain id `7331`, deployer genesis-funded, no external connectivity) or `rayls-testnet` (the external testnet). Applied by default for the default (lean) stack — in the hub-less default the public chain is the privacy nodes' only interconnection path: **`local` with `--local`, `rayls-testnet` otherwise**. `--full --local` also defaults to `local` (the 3.0.1 source deploy requires a public chain); only `--full` with pulled images runs without one. Adds per-participant `pubrelayer` services. * `--privacy-node-only`: Run just a single Axyl privacy node, with no bridge or surrounding services. Ignores all other flags. * `--monitoring`: Enable the observability stack (Grafana, Loki, Prometheus, Tempo) with eBPF auto-instrumentation. Default: off. -* `--blockscout `: Comma-separated participant letters that should get a Blockscout explorer (e.g. `a,b`). Opt-in by default; default-on (`a,b`) with `--full`. Pass an empty value to disable. -* `--local`: Dev mode. Build kos/CTS, pubrelayer and contracts from source (short names, `pull_policy=build`/`never`) instead of pulling them from ECR. Also defaults the topology to **hub-less** and `--public-chain` to `local`, so a `--local` init runs **everything on your machine with no hub** — pair with `--public-chain rayls-testnet` to keep bridging to the testnet instead. The infra images (NATS, the Private Network Hub, the private relayer, proofs-api, Postgres, nginx, Blockscout) still come from their registries and are pre-pulled one at a time, same as the published stack. +* `--blockscout `: Comma-separated participant letters that should get a Blockscout explorer (e.g. `a,b`). Defaults to **every participant**; use this to narrow the set. +* `--no-blockscout`: Disable the per-node Blockscout explorers entirely (overrides `--blockscout`). +* `--local`: Dev mode. Build the Rayls app components (kos/CTS, pubrelayer, private relayer, contracts — plus governance, proofs-api and audit-explorer in hub topologies) from source (short names, `pull_policy=build`/`never`) instead of pulling them from ECR. Also defaults the topology to **hub-less** and `--public-chain` to `local`, so a `--local` init runs **everything on your machine with no hub** — pair with `--public-chain rayls-testnet` to keep bridging to the testnet instead. The infra images (NATS, the Private Network Hub, Postgres, nginx, Blockscout) still come from their registries and are pre-pulled one at a time, same as the published stack. * `--no-pull`: Skip the image-pull step; `up` then fetches only the images missing locally, one at a time. Use to keep a locally-built image (e.g. a custom node/contracts build) instead of overwriting it from ECR. * `--lean`: **Deprecated** — the lean privacy-node → public-chain bridge is now the default, so this flag is a no-op. Use `--full` for the multi-participant stack. Example: ```bash -./rayls init --blockscout a # default: 1 Axyl node -> rayls-testnet + explorer (http://localhost:10004) -./rayls init --local --blockscout a # fully local: source builds + local Axyl public chain +./rayls init # default: 1 Axyl node -> rayls-testnet + explorer (http://localhost:10004) +./rayls init --local # fully local: source builds + local Axyl public chain ./rayls init --local --public-chain rayls-testnet # source builds, but bridge to the testnet ./rayls init --local --members 3 # 3 hub-less privacy nodes, fully local ./rayls init --local --with-hub # keep the minimal PNH (lean hub stack) @@ -158,11 +159,11 @@ The `init` flags compose into several distinct stack flavors: A **minimal privacy-node → public-chain bridge** for a single participant (`a`): the Axyl privacy node plus the services needed to bridge to an external public chain, with **no** private relayer, gnark/proofs API, or governance. This is what a bare `rayls init` does. ```bash -./rayls init --blockscout a # bridge to rayls-testnet (default) + privacy-chain explorer +./rayls init # bridge to rayls-testnet (default) + privacy-chain explorer ./rayls init --public-chain rayls-testnet # same; --public-chain overrides the target ``` -Hub-less (`--local`, the default topology) the stack is 7 services: `postgres` (shared Postgres — backs the pubrelayer + KOS databases), `nats`, `privacy-node-a`, `public-chain` (the in-stack Axyl public chain), `contracts`, `kos-a`, `pubrelayer-a`. KOS is kept because the pubrelayer fetches its signing keys from it. With `--with-hub` (and on pulled-image inits, until the published images support hub-less) the stack additionally runs `private-network-hub` (the Besu commit chain), `relayer-a` (the private relayer, PN↔PNH message relaying) and `proofs-api` (Enygma proofs). +Hub-less (`--local`, the default topology) the stack is 7 core services: `postgres` (shared Postgres — backs the pubrelayer + KOS databases), `nats`, `privacy-node-a`, `public-chain` (the in-stack Axyl public chain), `contracts`, `kos-a`, `pubrelayer-a` — plus the per-node Blockscout explorer services (default-on; `--no-blockscout` drops them). KOS is kept because the pubrelayer fetches its signing keys from it. With `--with-hub` (and on pulled-image inits, until the published images support hub-less) the stack additionally runs `private-network-hub` (the Besu commit chain), `relayer-a` (the private relayer, PN↔PNH message relaying) and `proofs-api` (Enygma proofs). Bridging to the testnet deploys from **your own funded key**: `rayls init` prompts for it and saves it to the stack `.env` (see [Funding](#funding)). Non-interactive/CI runs pass it via the environment instead: @@ -170,7 +171,7 @@ Bridging to the testnet deploys from **your own funded key**: `rayls init` promp PUBLIC_CHAIN_PRIVATE_KEY= ./rayls init --public-chain rayls-testnet # 0x prefix optional ``` -Once all 9 services are healthy, bridge a token end-to-end (see [Verifying the bridge](#verifying-the-bridge)). +Once the stack is healthy, bridge a token end-to-end (see [Verifying the bridge](#verifying-the-bridge)). > This mode uses the dedicated `rayls-contracts:lean-no-pnh` contracts image (built from the sibling `rayls-privacy-contracts` repo, branch `cli-lean-no-pnh`) and the `rayls-privacy-axyl` node image. Both are published to ECR, so the default `init` works out of the box. @@ -224,17 +225,17 @@ Adds Grafana (`:3300`), Loki, Prometheus, Tempo, and eBPF auto-instrumentation. #### 6. With Blockscout explorers -Adds a Blockscout backend + frontend per listed participant (proxied behind nginx). Opt-in by default; default-on with `--full`. +Runs a Blockscout backend + frontend per participant (proxied behind nginx), **by default in every mode**. `--blockscout ` narrows the set; `--no-blockscout` disables them. ```bash -./rayls init --blockscout a # explorer for the default single node -> http://localhost:10004 -./rayls init --full --blockscout a,b,c,d -./rayls init --full --blockscout "" # disable Blockscout +./rayls init # explorer for the default single node -> http://localhost:10004 +./rayls init --full --blockscout a,b # only participants a and b get explorers +./rayls init --no-blockscout # no explorers ``` -#### 7. Local images (dev mode) +#### 7. Local builds (dev mode) -Skips ECR pulls and uses locally built Rayls images. Combines with any of the above. Useful when iterating on Rayls source out-of-tree. +Builds the Rayls app components from source (pinned git refs, or your checkouts via `rayls dev`) instead of pulling their ECR images; infra images are still pulled. Combines with any of the above. Useful when iterating on Rayls source out-of-tree. ```bash ./rayls init --local @@ -335,7 +336,8 @@ After successful initialization, the exposed services depend on the mode. * **Privacy Node RPC:** `http://localhost:8545` — Axyl EVM JSON-RPC (chain id `12345`, gasless) * **Private Network Hub:** `http://localhost:3445` — the minimal commit chain (only with `--with-hub` or pulled-image inits; hub-less stacks have none) * **Pubrelayer:** `http://localhost:9050` — bridges to the public chain -* **KOS (Key Orchestration):** `http://localhost:8080` +* **KOS (Key Orchestration):** `localhost:8080` — the CTS gRPC endpoint (mTLS, not plain HTTP) +* **Blockscout explorer:** `http://localhost:10004` — privacy-chain explorer for node `a` (default-on; per-node at `10004 + 100·i`) **Full stack (`--full`) — additional shared services:** * **Block Explorer:** `http://localhost:8181` - View blockchain transactions and blocks @@ -363,7 +365,7 @@ A bare `rayls init` pulls the published `rayls-demo` images from ECR. `rayls ini | `--full` extras: governance-api/listener/flagger, proofs-api (gnark), audit-explorer | ECR | **built by Docker** from their `rayls-sovereign-*` repos on `main` (see note on gnark's Git-LFS keys below) | | Axyl privacy node | pulled from ECR | pulled from ECR + retagged (a local `rayls-privacy-axyl:latest` you built yourself is left alone) | | nats, Private Network Hub (Besu) | ECR | ECR (pulled — infra images aren't source-built) | -| Third-party (postgres, mongo-express, blockscout, nginx, grafana) | public registries | public registries | +| Third-party (postgres, blockscout, nginx, grafana) | public registries | public registries | The published `rayls-demo` service images are **multi-arch (amd64 + arm64)**, so remote `rayls init` runs natively on both Intel/AMD and Apple Silicon. Use `--local` when you want to build from a specific ref or hack on a component — not as an architecture workaround. diff --git a/cmd/init.go b/cmd/init.go index a18a423..1266e86 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -2,9 +2,10 @@ package cmd import ( "os" + "strings" + "github.com/raylsnetwork/rayls-sovereign-cli/internal/docker" "github.com/raylsnetwork/rayls-sovereign-cli/internal/stacks" - "strings" "github.com/fatih/color" "github.com/spf13/cobra" @@ -13,6 +14,7 @@ import ( var membersCount int var monitoringEnabled bool var blockscoutNodes string +var noBlockscout bool var localImages bool var publicChainPreset string var privacyNodeOnly bool @@ -39,12 +41,12 @@ primary use case. Hub-less is the default topology: nodes intercommunicate via the public chain only. --with-hub adds the minimal Private Network Hub; --full brings the complete hub demo stack. - rayls init --local # fully local hub-less system: source - # builds + a local Axyl public chain - rayls init --local --members 3 # 3 hub-less privacy nodes, all local rayls init # published images -> rayls-testnet # (keeps the minimal hub until the # published images support hub-less) + rayls init --local # fully local hub-less system: source + # builds + a local Axyl public chain + rayls init --local --members 3 # 3 hub-less privacy nodes, all local rayls init --with-hub # keep the minimal PNH explicitly rayls init --public-chain # bridge to 'local' or 'rayls-testnet' rayls init --privacy-node-only # just the Axyl node, no bridge @@ -70,11 +72,15 @@ the public chain only. --with-hub adds the minimal Private Network Hub; // Hub-less is the DEFAULT topology: the privacy nodes intercommunicate // via the public chain only. --with-hub opts the lean stack into the // minimal Private Network Hub; --full always brings the complete hub - // stack. Hub-less needs the HUB_ENABLED-aware contracts deploy and the - // hub-less-capable CTS (the rayls-sovereign-* 3.0.1 sources on main), which the - // published ECR images predate — pulled-image stacks therefore keep the - // minimal hub until the images are republished (same conditional default - // as the public-chain preset below; documented in --help and README). + // stack. Hub-less needs the hub-less-capable CTS (the rayls-sovereign-* + // 3.0.1 sources on main), which the published ECR images predate — the + // 3.0.0 CTS's ParticipantRegistrar calls + // ParticipantStorageV1.getChainViewData on its hub at startup, a + // function the PN-side replica doesn't implement, so it cannot run + // without a PNH even with the :lean-no-pnh deploy's PNH_ENABLED=false + // registry aliasing (verified empirically 2026-08-20). Pulled-image + // stacks therefore keep the minimal hub until the images are + // republished; --local stacks (3.0.1 source builds) run hub-less. noHub := !fullStack && !withHub && localImages // Resolve the public chain target. @@ -145,15 +151,19 @@ the public chain only. --with-hub adds the minimal Private Network Hub; participants := []string{"a", "b", "c", "d", "e", "f"}[:members] - // Blockscout: opt-in in the default (lean) mode so a bare init stays - // light; default-on for the --full demo stack. + // Blockscout: default-on for every participant in every mode (a bare + // init gets an explorer for node a). --blockscout narrows the set, + // --no-blockscout disables it entirely. var blockscout []string - if lean { + switch { + case noBlockscout: if cmd.Flags().Changed("blockscout") { - blockscout = parseBlockscoutNodes(blockscoutNodes, participants) + yellow.Println("Note: --no-blockscout overrides --blockscout; no Blockscout services will run.") } - } else { + case cmd.Flags().Changed("blockscout"): blockscout = parseBlockscoutNodes(blockscoutNodes, participants) + default: + blockscout = participants } if err := stacks.InitStack(participants, monitoringEnabled, blockscout, localImages, publicChain, lean, noHub, noPull); err != nil { @@ -166,12 +176,13 @@ the public chain only. --with-hub adds the minimal Private Network Hub; func init() { rootCmd.AddCommand(initCmd) initCmd.Flags().BoolVar(&fullStack, "full", false, "Bring up the full multi-participant demo stack (Private Network Hub, governance, multiple privacy nodes). Combine with --members and/or --public-chain.") - initCmd.Flags().BoolVar(&withHub, "with-hub", false, "Include the Private Network Hub (PNH) in the default (lean) stack: PNH plus the private relayer and proofs-api (PN<->PNH messaging, Enygma). Hub-less is the default topology for --local stacks; pulled-image stacks keep the hub regardless until the published images support hub-less. --full always includes the full hub (plus governance).") + initCmd.Flags().BoolVar(&withHub, "with-hub", false, "Include the Private Network Hub (PNH) in the default (lean) stack: PNH plus the private relayer and proofs-api (PN<->PNH messaging, Enygma). Hub-less is the default topology for --local stacks; pulled-image stacks keep the hub regardless (their 3.0.0 CTS requires one) until the published images support hub-less. --full always includes the full hub (plus governance).") initCmd.Flags().IntVar(&membersCount, "members", 2, "Number of privacy node participants: 2-6 with --full, 1-6 for the hub-less default (default 1 there). Ignored on hub-carrying lean stacks.") initCmd.Flags().StringVar(&publicChainPreset, "public-chain", "", "Public chain preset to bridge to: 'local' (an Axyl public chain inside the stack) or 'rayls-testnet'. Defaults: lean stacks get 'local' with --local and 'rayls-testnet' otherwise; --full --local gets 'local' (the 3.0.1 source deploy requires a public chain). Only --full with pulled images runs without one.") initCmd.Flags().BoolVar(&privacyNodeOnly, "privacy-node-only", false, "Run just a single Axyl privacy node, with no bridge or surrounding services. Ignores other flags.") initCmd.Flags().BoolVar(&monitoringEnabled, "monitoring", false, "Enable monitoring stack (Grafana, Loki, Tempo, Prometheus)") - initCmd.Flags().StringVar(&blockscoutNodes, "blockscout", "a,b", "Comma-separated list of nodes to enable Blockscout for (e.g. 'a,b,c'). Opt-in by default; default-on with --full.") + initCmd.Flags().StringVar(&blockscoutNodes, "blockscout", "", "Comma-separated list of nodes to enable Blockscout for (e.g. 'a,b,c'). Defaults to every participant; use this to narrow the set, or --no-blockscout to disable.") + initCmd.Flags().BoolVar(&noBlockscout, "no-blockscout", false, "Disable the per-node Blockscout explorers (they run for every participant by default).") initCmd.Flags().BoolVar(&localImages, "local", false, "Dev mode: build Rayls components (kos/CTS, pubrelayer, contracts) from source inside Docker — pinned git contexts by default, local checkouts via `rayls dev` — instead of pulling images from ECR. Also defaults --public-chain to 'local', making the stack fully self-contained.") initCmd.Flags().BoolVar(&noPull, "no-pull", false, "Skip the image pull step; `up` fetches only missing images. Use to keep a locally-built image (e.g. a custom contracts build) instead of overwriting it from ECR.") // --lean is now the default behavior; keep the flag as a deprecated no-op so diff --git a/internal/docker/docker_config.go b/internal/docker/docker_config.go index b989509..9825dc6 100644 --- a/internal/docker/docker_config.go +++ b/internal/docker/docker_config.go @@ -840,7 +840,12 @@ func getContractsService(participants []string, local bool, pc *PublicChain, lea // the privacy node in the .X.env — which a 3.0.1 CTS would read as "hub // enabled, at the PN" (silently wrong topology). Left defaulted (true), // such an image tries to deploy the PNH, dials the absent private-hub - // host and fails loudly instead. + // host and fails loudly instead. (The aliasing is also insufficient for + // a genuinely hub-less PULLED stack: the 3.0.0 CTS's ParticipantRegistrar + // calls ParticipantStorageV1.getChainViewData on its "hub" at startup, + // which the PN-side ParticipantStorageReplicaV1 doesn't implement — + // verified empirically 2026-08-20 — so hub-less stays --local-only until + // the CTS images are republished from >= version/3.0.1.) env = append(env, "HUB_ENABLED=false") // deployCoreContractsBatch ABI-encodes process.env.PNH_CHAIN_ID into // EndpointV1.initialize even hub-less (unset crashes ethers with "invalid @@ -1572,7 +1577,13 @@ func getBlockscoutBackendService(node string, portBase, chainID, participantIdx privacyNode := fmt.Sprintf("privacy-node-%s", node) return &Service{ - Image: "blockscout/blockscout:latest", + // Backend and frontend must be a same-era matched pair: Docker Hub + // `blockscout/blockscout:latest` is months stale while the ghcr frontend + // moves, and the skew breaks the UI (CORS preflight rejects the + // updated-gas-oracle header; /api/v2/search renamed address -> + // address_hash, crashing the search bar). If either is bumped, bump both + // and smoke-test those two endpoints. + Image: "ghcr.io/blockscout/blockscout:9.0.2", Restart: "always", StopGracePeriod: "5m", Command: `sh -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"`, @@ -1626,7 +1637,9 @@ func getBlockscoutFrontendService(node string, portBase, chainID, participantIdx nodeUpper := strings.ToUpper(node) return &Service{ - Image: "ghcr.io/blockscout/frontend:latest", + // Pinned as the matched pair of ghcr.io/blockscout/blockscout:9.0.2 — + // see getBlockscoutBackendService before bumping. + Image: "ghcr.io/blockscout/frontend:v2.3.5", Restart: "always", Ports: []string{ fmt.Sprintf("127.0.0.1:%d:3000", portBase+3), diff --git a/internal/docker/docker_config_nohub_test.go b/internal/docker/docker_config_nohub_test.go index 83c8b1b..86971ef 100644 --- a/internal/docker/docker_config_nohub_test.go +++ b/internal/docker/docker_config_nohub_test.go @@ -206,6 +206,7 @@ func TestDependsOnConditionsSatisfiable(t *testing.T) { "lean+pc": GetDemoComposeConfig([]string{"a"}, false, nil, false, &pc, true, false, nil), "full+pc+no-hub": GetDemoComposeConfig([]string{"a", "b"}, false, nil, false, &pc, false, true, nil), "lean+pc+no-hub": GetDemoComposeConfig([]string{"a"}, false, nil, false, &pc, true, true, nil), + "lean+pc+bs": GetDemoComposeConfig([]string{"a"}, false, []string{"a"}, false, &pc, true, false, nil), "lean+localpc": GetDemoComposeConfig([]string{"a"}, false, nil, true, &localPC, true, false, nil), "nohub+localpc": GetDemoComposeConfig([]string{"a", "b"}, false, nil, true, &localPC, false, true, nil), "privacy-node-only": GetPrivacyNodeOnlyConfig(false), diff --git a/internal/docker/docker_config_pull_test.go b/internal/docker/docker_config_pull_test.go index d0eb958..ef039e3 100644 --- a/internal/docker/docker_config_pull_test.go +++ b/internal/docker/docker_config_pull_test.go @@ -112,7 +112,9 @@ func TestBlockscoutBackendPullsPublishedImage(t *testing.T) { if svc.Build != nil { t.Errorf("blockscout-backend-a should have no build section, got context %q", svc.Build.Context) } - if svc.Image != "blockscout/blockscout:latest" { - t.Errorf("blockscout-backend-a image = %q, want blockscout/blockscout:latest", svc.Image) + // Backend + frontend are a pinned matched pair (Docker Hub :latest is + // stale and skews against the ghcr frontend, breaking CORS and search). + if svc.Image != "ghcr.io/blockscout/blockscout:9.0.2" { + t.Errorf("blockscout-backend-a image = %q, want ghcr.io/blockscout/blockscout:9.0.2", svc.Image) } } From 1bc0717e8af9a414b8c4a424f273050fbf6b3f9b Mon Sep 17 00:00:00 2001 From: bdimitrov-netzine Date: Fri, 21 Aug 2026 17:21:23 +0300 Subject: [PATCH 4/7] feat: removed PNH from default deployment --- DEV_MODE.md | 13 ++++---- README.md | 10 +++---- cmd/init.go | 33 ++++++++++----------- internal/docker/docker_config.go | 25 ++++++---------- internal/docker/docker_config_local_test.go | 8 ++--- internal/docker/docker_config_nohub_test.go | 4 +-- internal/docker/sources.go | 32 ++++++++------------ internal/docker/sources_test.go | 15 +++++----- 8 files changed, 58 insertions(+), 82 deletions(-) diff --git a/DEV_MODE.md b/DEV_MODE.md index f9aa780..03fd25a 100644 --- a/DEV_MODE.md +++ b/DEV_MODE.md @@ -47,14 +47,11 @@ infra ones — `nats`, `private-network-hub` (Besu) — plus the `axyl` privacy > **ops-api excluded on purpose.** The new `rayls-sovereign-ops-api` (the former > backend) is not run by the CLI and is not in the registry. -> ⚠️ **The sovereign repos are currently PRIVATE**, so the default git contexts -> use **SSH** (`git@github.com:…`) and the build forwards your **ssh-agent** -> (`build.ssh: [default]`, added automatically for git@/ssh:// URLs). Make sure a -> key with `raylsnetwork` access is loaded — `ssh-add -l` must list one (if it's -> empty, `ssh-add ~/.ssh/id_ed25519` or your key). **TODO:** once the admin makes -> the repos public, switch the `Repo` URLs in `internal/docker/sources.go` back -> to `https://github.com/raylsnetwork/.git` — the build then clones -> anonymously and drops the ssh-agent requirement. +The sovereign repos are **public**, so the default git contexts use **https** — +no ssh-agent needed. If you override a component to a private fork with a +`git@github.com:…` / `ssh://…` URL (via `_REPO`), the build forwards your +**ssh-agent** (`build.ssh: [default]`) automatically; make sure your key is loaded +(`ssh-add -l`). ### Overriding refs / repos (per stack, via `.env`) diff --git a/README.md b/README.md index ec412a7..384d23c 100644 --- a/README.md +++ b/README.md @@ -121,13 +121,13 @@ To set up a new environment, run the `init` command. This will: ./rayls init ``` -By default this spins up **Axyl privacy node(s) bridged to a public chain**, with **hub-less as the default topology**: the nodes intercommunicate via the public chain only. With `--local` everything runs on your machine (source builds + an in-stack Axyl public chain). Pulled-image inits (no `--local`) bridge to the Rayls testnet and keep a minimal Private Network Hub for now (the published images predate hub-less support), deploying from **your own funded testnet key**: `rayls init` prompts for it and saves it to the stack `.env` (see [Funding](#funding)). Every participant also gets a Blockscout explorer by default (`--no-blockscout` disables). `--with-hub` opts a lean stack into the minimal hub explicitly; `--full` brings the complete hub demo stack. +By default this spins up **Axyl privacy node(s) bridged to a public chain**, with **hub-less as the default topology**: the nodes intercommunicate via the public chain only, no Private Network Hub. With `--local` everything runs on your machine (source builds + an in-stack Axyl public chain). Pulled-image inits (no `--local`) bridge to the Rayls testnet, deploying from **your own funded testnet key**: `rayls init` prompts for it and saves it to the stack `.env` (see [Funding](#funding)). Every participant also gets a Blockscout explorer by default (`--no-blockscout` disables). `--with-hub` opts a lean stack into the minimal hub explicitly; `--full` brings the complete hub demo stack. If a `docker-compose.yaml` already exists, you'll be prompted to overwrite or use the existing file. If you choose to keep the existing file, the CLI will proceed with pulling images and starting containers. **Options:** * `--full`: Bring up the full multi-participant demo stack (local Private Network Hub / commit chain, governance, proofs API, multiple privacy nodes). Combine with `--members` and/or `--public-chain`. -* `--with-hub`: Include the **Private Network Hub** in the default (lean) stack — a **functional hub**: PNH plus the private relayer and proofs-api, so PN↔PNH messaging and Enygma work. Without it, **hub-less is the default**: no PNH, no private relayer, no proofs API — the privacy nodes intercommunicate via the public chain only. Hub-less currently applies to `--local` stacks only: it needs the 3.0.1 `rayls-sovereign-contracts` / `rayls-sovereign-relayer` sources (on `main`), which the published ECR images predate, so pulled-image inits keep the hub until the images catch up. Every `--local` init records its build sources in the stack `.env` — the local sibling checkouts (`../rayls-sovereign-contracts`, `../rayls-sovereign-relayer`, recorded as relative paths) via `CONTRACTS_SRC`/`RELAYER_SRC` when present (whatever branch is checked out there is what builds), else the `main` git contexts via `CONTRACTS_REF`/`RELAYER_REF`. `--full` always includes the full hub. +* `--with-hub`: Include the **Private Network Hub** in the default (lean) stack — a **functional hub**: PNH plus the private relayer and proofs-api, so PN↔PNH messaging and Enygma work. Without it, **hub-less is the default** (both pulled-image and `--local` stacks): no PNH, no private relayer, no proofs API — the privacy nodes intercommunicate via the public chain only, and the contracts deploy runs with `HUB_ENABLED=false`. Every `--local` init records its build sources in the stack `.env` — the local sibling checkouts (`../rayls-sovereign-contracts`, `../rayls-sovereign-relayer`, recorded as relative paths) via `CONTRACTS_SRC`/`RELAYER_SRC` when present (whatever branch is checked out there is what builds), else the `main` git contexts via `CONTRACTS_REF`/`RELAYER_REF`. `--full` always includes the full hub. * `--members `: Number of privacy node participants. With `--full`: 2–6 (default **2**). For the hub-less default topology: 1–6 (default **1**) — the nodes intercommunicate via the public chain, so any count is meaningful. Ignored on hub-carrying lean stacks (`--with-hub` runs a single participant; use `--full` for the multi-participant hub). * `--public-chain `: Public chain preset to bridge to — `local` (an Axyl public chain running **inside the stack**: service `public-chain`, RPC `localhost:8845`, chain id `7331`, deployer genesis-funded, no external connectivity) or `rayls-testnet` (the external testnet). Applied by default for the default (lean) stack — in the hub-less default the public chain is the privacy nodes' only interconnection path: **`local` with `--local`, `rayls-testnet` otherwise**. `--full --local` also defaults to `local` (the 3.0.1 source deploy requires a public chain); only `--full` with pulled images runs without one. Adds per-participant `pubrelayer` services. * `--privacy-node-only`: Run just a single Axyl privacy node, with no bridge or surrounding services. Ignores all other flags. @@ -163,7 +163,7 @@ A **minimal privacy-node → public-chain bridge** for a single participant (`a` ./rayls init --public-chain rayls-testnet # same; --public-chain overrides the target ``` -Hub-less (`--local`, the default topology) the stack is 7 core services: `postgres` (shared Postgres — backs the pubrelayer + KOS databases), `nats`, `privacy-node-a`, `public-chain` (the in-stack Axyl public chain), `contracts`, `kos-a`, `pubrelayer-a` — plus the per-node Blockscout explorer services (default-on; `--no-blockscout` drops them). KOS is kept because the pubrelayer fetches its signing keys from it. With `--with-hub` (and on pulled-image inits, until the published images support hub-less) the stack additionally runs `private-network-hub` (the Besu commit chain), `relayer-a` (the private relayer, PN↔PNH message relaying) and `proofs-api` (Enygma proofs). +Hub-less (the default topology) the stack is 7 core services: `postgres` (shared Postgres — backs the pubrelayer + KOS databases), `nats`, `privacy-node-a`, `public-chain` (the in-stack Axyl public chain with `--local`; omitted when bridging to the testnet), `contracts`, `kos-a`, `pubrelayer-a` — plus the per-node Blockscout explorer services (default-on; `--no-blockscout` drops them). KOS is kept because the pubrelayer fetches its signing keys from it. With `--with-hub` the stack additionally runs `private-network-hub` (the Besu commit chain), `relayer-a` (the private relayer, PN↔PNH message relaying) and `proofs-api` (Enygma proofs). Bridging to the testnet deploys from **your own funded key**: `rayls init` prompts for it and saves it to the stack `.env` (see [Funding](#funding)). Non-interactive/CI runs pass it via the environment instead: @@ -202,7 +202,7 @@ The default topology runs the environment **without the Private Network Hub**, m ./rayls init --local --public-chain rayls-testnet # hub-less, bridged to the testnet ``` -> Hub-less needs the `HUB_ENABLED`-aware contracts deploy and the hub-less-capable CTS, which ship in the 3.0.1 `rayls-sovereign-contracts` / `rayls-sovereign-relayer` repos (on `main`). The published ECR images predate that, so hub-less currently applies to `--local` inits only (pulled-image inits keep the minimal hub); the CLI records the build sources in the stack `.env` — preferring the local sibling checkouts (`CONTRACTS_SRC`/`RELAYER_SRC`, so in-flight hub-less branches build as checked out) and falling back to the `main` git contexts (`CONTRACTS_REF`/`RELAYER_REF`). Override either in `.env` if needed. +> Hub-less needs the `HUB_ENABLED`-aware contracts deploy and the hub-less-capable CTS, which ship in the 3.0.1 `rayls-sovereign-contracts` / `rayls-sovereign-relayer` components — now published to ECR, so hub-less is the default for pulled-image inits too. For `--local`, the CLI records the build sources in the stack `.env` — preferring the local sibling checkouts (`CONTRACTS_SRC`/`RELAYER_SRC`, so in-flight hub-less branches build as checked out) and falling back to the `main` git contexts (`CONTRACTS_REF`/`RELAYER_REF`). Override either in `.env` if needed. #### 4. Privacy node only @@ -334,7 +334,7 @@ After successful initialization, the exposed services depend on the mode. **Default (local privacy node → public chain):** * **Privacy Node RPC:** `http://localhost:8545` — Axyl EVM JSON-RPC (chain id `12345`, gasless) -* **Private Network Hub:** `http://localhost:3445` — the minimal commit chain (only with `--with-hub` or pulled-image inits; hub-less stacks have none) +* **Private Network Hub:** `http://localhost:3445` — the minimal commit chain (only with `--with-hub` or `--full`; the hub-less default has none) * **Pubrelayer:** `http://localhost:9050` — bridges to the public chain * **KOS (Key Orchestration):** `localhost:8080` — the CTS gRPC endpoint (mTLS, not plain HTTP) * **Blockscout explorer:** `http://localhost:10004` — privacy-chain explorer for node `a` (default-on; per-node at `10004 + 100·i`) diff --git a/cmd/init.go b/cmd/init.go index 1266e86..d79534f 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -41,13 +41,12 @@ primary use case. Hub-less is the default topology: nodes intercommunicate via the public chain only. --with-hub adds the minimal Private Network Hub; --full brings the complete hub demo stack. - rayls init # published images -> rayls-testnet - # (keeps the minimal hub until the - # published images support hub-less) rayls init --local # fully local hub-less system: source # builds + a local Axyl public chain rayls init --local --members 3 # 3 hub-less privacy nodes, all local - rayls init --with-hub # keep the minimal PNH explicitly + rayls init # published images -> rayls-testnet, + # hub-less (private node <-> public chain) + rayls init --with-hub # add the minimal PNH explicitly rayls init --public-chain # bridge to 'local' or 'rayls-testnet' rayls init --privacy-node-only # just the Axyl node, no bridge rayls init --full [--members N] # full multi-participant demo stack @@ -69,19 +68,17 @@ the public chain only. --with-hub adds the minimal Private Network Hub; // the default and is kept only as a deprecated no-op.) lean := !fullStack - // Hub-less is the DEFAULT topology: the privacy nodes intercommunicate - // via the public chain only. --with-hub opts the lean stack into the - // minimal Private Network Hub; --full always brings the complete hub - // stack. Hub-less needs the hub-less-capable CTS (the rayls-sovereign-* - // 3.0.1 sources on main), which the published ECR images predate — the - // 3.0.0 CTS's ParticipantRegistrar calls - // ParticipantStorageV1.getChainViewData on its hub at startup, a - // function the PN-side replica doesn't implement, so it cannot run - // without a PNH even with the :lean-no-pnh deploy's PNH_ENABLED=false - // registry aliasing (verified empirically 2026-08-20). Pulled-image - // stacks therefore keep the minimal hub until the images are - // republished; --local stacks (3.0.1 source builds) run hub-less. - noHub := !fullStack && !withHub && localImages + // Hub-less is the DEFAULT topology in every non-full mode: the privacy + // nodes intercommunicate via the public chain only, and the contracts + // deploy runs with HUB_ENABLED=false (no Private Network Hub). --with-hub + // opts the lean stack back into the minimal PNH; --full always brings the + // complete hub stack. This holds for pulled-image inits too now that the + // published 3.0.1 ECR images carry the HUB_ENABLED-aware contracts deploy + // and the hub-less-capable CTS (the 3.0.0 CTS's ParticipantRegistrar + // called ParticipantStorageV1.getChainViewData on its hub at startup and + // could not run PNH-less; the 3.0.1 CTS keys hub-less off the absence of + // PNH_DEPLOYMENT_PROXY_REGISTRY, which is why the images had to ship first). + noHub := !fullStack && !withHub // Resolve the public chain target. // - default (lean): always bridges; --public-chain overrides. @@ -176,7 +173,7 @@ the public chain only. --with-hub adds the minimal Private Network Hub; func init() { rootCmd.AddCommand(initCmd) initCmd.Flags().BoolVar(&fullStack, "full", false, "Bring up the full multi-participant demo stack (Private Network Hub, governance, multiple privacy nodes). Combine with --members and/or --public-chain.") - initCmd.Flags().BoolVar(&withHub, "with-hub", false, "Include the Private Network Hub (PNH) in the default (lean) stack: PNH plus the private relayer and proofs-api (PN<->PNH messaging, Enygma). Hub-less is the default topology for --local stacks; pulled-image stacks keep the hub regardless (their 3.0.0 CTS requires one) until the published images support hub-less. --full always includes the full hub (plus governance).") + initCmd.Flags().BoolVar(&withHub, "with-hub", false, "Include the Private Network Hub (PNH) in the default (lean) stack: PNH plus the private relayer and proofs-api (PN<->PNH messaging, Enygma). Hub-less is the default topology for both pulled-image and --local stacks; --full always includes the full hub (plus governance).") initCmd.Flags().IntVar(&membersCount, "members", 2, "Number of privacy node participants: 2-6 with --full, 1-6 for the hub-less default (default 1 there). Ignored on hub-carrying lean stacks.") initCmd.Flags().StringVar(&publicChainPreset, "public-chain", "", "Public chain preset to bridge to: 'local' (an Axyl public chain inside the stack) or 'rayls-testnet'. Defaults: lean stacks get 'local' with --local and 'rayls-testnet' otherwise; --full --local gets 'local' (the 3.0.1 source deploy requires a public chain). Only --full with pulled images runs without one.") initCmd.Flags().BoolVar(&privacyNodeOnly, "privacy-node-only", false, "Run just a single Axyl privacy node, with no bridge or surrounding services. Ignores other flags.") diff --git a/internal/docker/docker_config.go b/internal/docker/docker_config.go index 9825dc6..cd2116f 100644 --- a/internal/docker/docker_config.go +++ b/internal/docker/docker_config.go @@ -923,18 +923,13 @@ func getContractsService(participants []string, local bool, pc *PublicChain, lea env = append(env, "PUBLIC_CHAIN_ENABLED=${PUBLIC_CHAIN_ENABLED:-false}") } - // PULLED lean-with-hub mode uses a dedicated image tag that carries the - // PNH_ENABLED-aware deploy (built from the old rayls-privacy-contracts - // branch cli-lean-no-pnh). This keeps :latest — the non-lean default — - // untouched. Every --local stack (lean hub-less, lean with-hub, full) - // instead builds the mainline HUB_ENABLED-aware deploy from the pinned - // rayls-sovereign-contracts main sources (see the stack .env pins) and - // tags it :latest — one image serves all local topologies, HUB_ENABLED - // switching the deploy path. + // Pulled modes use :latest — the 3.0.1 image with the HUB_ENABLED-aware + // deploy; one image serves every topology (lean hub-less, lean with-hub, + // full), HUB_ENABLED switching the deploy path. The :lean-no-pnh tag now + // points at the same digest and is kept only for backward compatibility. + // --local stacks build the same deploy from the pinned + // rayls-sovereign-contracts main sources instead (see the stack .env pins). contractsRef := "public.ecr.aws/w0k9o1t3/rayls-demo/rayls-contracts:latest" - if lean && !noHub && !local { - contractsRef = "public.ecr.aws/w0k9o1t3/rayls-demo/rayls-contracts:lean-no-pnh" - } image, pullPolicy := localImage(contractsRef, local) svc := &Service{ Image: image, @@ -1351,11 +1346,9 @@ func GetDemoComposeConfig(participants []string, monitoring bool, blockscout []s // the cts-

alias) at startup. The depends_on graphs of the surviving // services are rewritten so they no longer wait on removed ones. // -// The topology is deliberately NOT forked on image provenance: the published -// ECR images still predate the 3.0.1 (rayls-sovereign-*) components this shape -// needs (the private relayer's config contract in particular), but they are being -// republished from the current codebases — the shape targets that, not -// today's registry state. +// The topology is uniform across image provenance: the published 3.0.1 ECR +// images and the --local source builds both carry the components this shape +// needs (the private relayer's config contract, the hub-less-capable CTS). func applyLeanNoPNH(compose *DockerCompose, participants []string) { drop := []string{ "governance-api", "governance-listener", "governance-flagger", diff --git a/internal/docker/docker_config_local_test.go b/internal/docker/docker_config_local_test.go index 60db88e..2500747 100644 --- a/internal/docker/docker_config_local_test.go +++ b/internal/docker/docker_config_local_test.go @@ -76,17 +76,15 @@ func TestGetDemoComposeConfigLocalFromSource(t *testing.T) { if kosA.Build == nil { t.Fatalf("kos-a should have a build section") } - wantCtx := "${RELAYER_SRC:-git@github.com:raylsnetwork/rayls-sovereign-relayer.git#main}" + wantCtx := "${RELAYER_SRC:-https://github.com/raylsnetwork/rayls-sovereign-relayer.git#main}" if kosA.Build.Context != wantCtx { t.Errorf("kos-a context = %q, want %q", kosA.Build.Context, wantCtx) } if kosA.Build.Dockerfile != "cts/Dockerfile" { t.Errorf("kos-a dockerfile = %q, want cts/Dockerfile (production)", kosA.Build.Dockerfile) } - // The sovereign repos are currently private git@ URLs, so the build forwards - // the ssh agent. (Flip to len==0 once they go public and the URLs become https.) - if len(kosA.Build.Ssh) != 1 || kosA.Build.Ssh[0] != "default" { - t.Errorf("kos-a should forward the ssh agent for the private git@ context, got %v", kosA.Build.Ssh) + if len(kosA.Build.Ssh) != 0 { + t.Errorf("kos-a should not forward the ssh agent for the public https git context, got %v", kosA.Build.Ssh) } if kosA.PullPolicy != "build" { t.Errorf("kos-a pull_policy = %q, want build", kosA.PullPolicy) diff --git a/internal/docker/docker_config_nohub_test.go b/internal/docker/docker_config_nohub_test.go index 86971ef..2aa00ab 100644 --- a/internal/docker/docker_config_nohub_test.go +++ b/internal/docker/docker_config_nohub_test.go @@ -180,14 +180,14 @@ func TestGetDemoComposeConfigNoHubLocalFromSource(t *testing.T) { if contracts.Build.Dockerfile != "Dockerfile.dev" { t.Errorf("contracts dockerfile = %q, want Dockerfile.dev", contracts.Build.Dockerfile) } - if want := "${CONTRACTS_SRC:-git@github.com:raylsnetwork/rayls-sovereign-contracts.git#main}"; contracts.Build.Context != want { + if want := "${CONTRACTS_SRC:-https://github.com/raylsnetwork/rayls-sovereign-contracts.git#main}"; contracts.Build.Context != want { t.Errorf("contracts context = %q, want %q", contracts.Build.Context, want) } kosA := compose.Services["kos-a"] if kosA.Build == nil { t.Fatalf("kos-a should have a build section in --local") } - if want := "${RELAYER_SRC:-git@github.com:raylsnetwork/rayls-sovereign-relayer.git#main}"; kosA.Build.Context != want { + if want := "${RELAYER_SRC:-https://github.com/raylsnetwork/rayls-sovereign-relayer.git#main}"; kosA.Build.Context != want { t.Errorf("kos-a context = %q, want %q", kosA.Build.Context, want) } } diff --git a/internal/docker/sources.go b/internal/docker/sources.go index dd86e72..a8d708e 100644 --- a/internal/docker/sources.go +++ b/internal/docker/sources.go @@ -43,26 +43,18 @@ type Component struct { } // Components is the registry of Rayls components buildable from source in -// --local mode. Each points at its GitHub repo on `main`: the rayls-sovereign-* -// repos hold the 3.0.1 code copied over as a single `main` branch (no version -// tags yet), so `main` is the build ref. -// -// TODO(sovereign-public): the rayls-sovereign-* repos are currently PRIVATE, so -// the default context uses SSH (git@...). BuildSection adds build.ssh:[default] -// for git@/ssh:// URLs, so `docker compose up --build` forwards your ssh-agent -// — run `ssh-add -l` and make sure a key with raylsnetwork access is loaded. -// Once the admin makes the repos public, switch every Repo below back to -// https://github.com/raylsnetwork/.git (anonymous clone, no ssh-agent); -// BuildSection then drops the ssh forwarding automatically. See also the -// matching notes in README.md / DEV_MODE.md. -// -// Infra images (nats, private-network-hub, axyl) are not source-built — see -// localImage/ensureAxylImage. +// --local mode. Each points at its public GitHub repo on `main`: the +// rayls-sovereign-* repos hold the 3.0.1 code copied over as a single `main` +// branch (no version tags yet), so `main` is the build ref. The repos are +// public, so the default context is an https git URL — no ssh-agent needed +// (BuildSection only requests ssh forwarding for git@/ssh:// overrides, e.g. a +// private fork). Infra images (nats, private-network-hub, axyl) are not +// source-built — see localImage/ensureAxylImage. var Components = []Component{ { Key: "contracts", EnvPrefix: "CONTRACTS", - Repo: "git@github.com:raylsnetwork/rayls-sovereign-contracts.git", + Repo: "https://github.com/raylsnetwork/rayls-sovereign-contracts.git", DirName: "rayls-sovereign-contracts", DefaultRef: "main", Watch: false, // contract changes need an explicit redeploy, not a file watcher @@ -76,7 +68,7 @@ var Components = []Component{ { Key: "relayer", EnvPrefix: "RELAYER", - Repo: "git@github.com:raylsnetwork/rayls-sovereign-relayer.git", + Repo: "https://github.com/raylsnetwork/rayls-sovereign-relayer.git", DirName: "rayls-sovereign-relayer", GlossaryPath: relayerPathV3, // deploy still writes /parfin/rayls-privacy-relayer-api // The sovereign repos carry only a single `main` branch (the copied-over @@ -95,7 +87,7 @@ var Components = []Component{ { Key: "governance", EnvPrefix: "GOVERNANCE", - Repo: "git@github.com:raylsnetwork/rayls-sovereign-pnh-governance.git", + Repo: "https://github.com/raylsnetwork/rayls-sovereign-pnh-governance.git", DirName: "rayls-sovereign-pnh-governance", GlossaryPath: governancePathV3, // deploy writes /parfin/rayls-privacy-pnh-governance-api DefaultRef: "main", @@ -109,7 +101,7 @@ var Components = []Component{ { Key: "gnark", EnvPrefix: "GNARK", - Repo: "git@github.com:raylsnetwork/rayls-sovereign-gnark-api.git", + Repo: "https://github.com/raylsnetwork/rayls-sovereign-gnark-api.git", DirName: "rayls-sovereign-gnark-api", DefaultRef: "main", Watch: false, @@ -125,7 +117,7 @@ var Components = []Component{ { Key: "auditor", EnvPrefix: "AUDITOR", - Repo: "git@github.com:raylsnetwork/rayls-sovereign-pnh-auditor-ui.git", + Repo: "https://github.com/raylsnetwork/rayls-sovereign-pnh-auditor-ui.git", DirName: "rayls-sovereign-pnh-auditor-ui", DefaultRef: "main", Watch: false, // Angular -> nginx; no hot reload diff --git a/internal/docker/sources_test.go b/internal/docker/sources_test.go index 6a81bd1..1539106 100644 --- a/internal/docker/sources_test.go +++ b/internal/docker/sources_test.go @@ -33,10 +33,10 @@ func TestSourcesDefaults(t *testing.T) { } } c := *ComponentByKey("relayer") - if got := srcs.Repo(c); got != "git@github.com:raylsnetwork/rayls-sovereign-relayer.git" { + if got := srcs.Repo(c); got != "https://github.com/raylsnetwork/rayls-sovereign-relayer.git" { t.Errorf("default repo = %q", got) } - want := "${RELAYER_SRC:-git@github.com:raylsnetwork/rayls-sovereign-relayer.git#main}" + want := "${RELAYER_SRC:-https://github.com/raylsnetwork/rayls-sovereign-relayer.git#main}" if got := srcs.BuildContext(c); got != want { t.Errorf("context = %q, want %q", got, want) } @@ -76,16 +76,15 @@ func TestSourcesEnvFileAndProcessPrecedence(t *testing.T) { } func TestSourcesHTTPSRepoDropsSSH(t *testing.T) { - // The sovereign repos are currently private git@ URLs, so the default - // context requests ssh-agent forwarding. An https override (a public fork, - // or once the repos go public) drops it. - t.Setenv("CONTRACTS_REPO", "https://github.com/raylsnetwork/rayls-sovereign-contracts.git") + // The default sovereign repos are public https URLs — no ssh-agent needed. + // A git@/ssh:// override (e.g. a private fork) opts back into forwarding. + t.Setenv("RELAYER_REPO", "git@github.com:you/rayls-sovereign-relayer.git") srcs := resolveIn(t, "") if b := srcs.BuildSection("contracts", "contracts"); len(b.Ssh) != 0 { - t.Errorf("https override should not request ssh forwarding, got %v", b.Ssh) + t.Errorf("default https context should not request ssh forwarding, got %v", b.Ssh) } if b := srcs.BuildSection("relayer", "kos"); len(b.Ssh) != 1 { - t.Errorf("default git@ context should request ssh agent forwarding, got %v", b.Ssh) + t.Errorf("git@ override should request ssh agent forwarding, got %v", b.Ssh) } } From 7a5ef3c4eeee4c05cf58e37b1ced0a1fa06837db Mon Sep 17 00:00:00 2001 From: bdimitrov-netzine Date: Fri, 21 Aug 2026 18:27:37 +0300 Subject: [PATCH 5/7] chore: updated manifest generation URLs --- README.md | 8 ++++---- cmd/update.go | 4 +++- scripts/generate-manifest.sh | 10 +++++----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 384d23c..3da719c 100644 --- a/README.md +++ b/README.md @@ -65,28 +65,28 @@ Download the latest binary for your platform: **macOS (Apple Silicon):** ```bash -curl -fL https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-darwin-arm64 -o rayls +curl -fL https://cli.rayls.com/rayls-darwin-arm64 -o rayls chmod +x rayls sudo mv rayls /usr/local/bin/ ``` **macOS (Intel):** ```bash -curl -fL https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-darwin-amd64 -o rayls +curl -fL https://cli.rayls.com/rayls-darwin-amd64 -o rayls chmod +x rayls sudo mv rayls /usr/local/bin/ ``` **Linux (x86_64):** ```bash -curl -fL https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-linux-amd64 -o rayls +curl -fL https://cli.rayls.com/rayls-linux-amd64 -o rayls chmod +x rayls sudo mv rayls /usr/local/bin/ ``` **Linux (ARM64):** ```bash -curl -fL https://rayls-cli.s3.eu-west-2.amazonaws.com/rayls-linux-arm64 -o rayls +curl -fL https://cli.rayls.com/rayls-linux-arm64 -o rayls chmod +x rayls sudo mv rayls /usr/local/bin/ ``` diff --git a/cmd/update.go b/cmd/update.go index 8aa5540..f6202ae 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -16,7 +16,9 @@ import ( ) const ( - manifestURL = "https://rayls-cli.s3.eu-west-2.amazonaws.com/manifest.json" + // Binaries + manifest are served via the Cloudflare CDN in front of the + // (private) rayls-cli S3 bucket; the direct S3 URL is not publicly reachable. + manifestURL = "https://cli.rayls.com/manifest.json" cacheTTL = 24 * time.Hour ) diff --git a/scripts/generate-manifest.sh b/scripts/generate-manifest.sh index 61e0b42..f5a91e0 100755 --- a/scripts/generate-manifest.sh +++ b/scripts/generate-manifest.sh @@ -28,19 +28,19 @@ cat > manifest.json < Date: Fri, 21 Aug 2026 19:18:45 +0100 Subject: [PATCH 6/7] refactor: address SonarCloud findings (relayer- prefix constant, table-driven key-check test) --- internal/docker/docker_config.go | 11 +- internal/stacks/publicchainkey_test.go | 141 +++++++++---------------- 2 files changed, 58 insertions(+), 94 deletions(-) diff --git a/internal/docker/docker_config.go b/internal/docker/docker_config.go index cd2116f..c9c64fb 100644 --- a/internal/docker/docker_config.go +++ b/internal/docker/docker_config.go @@ -471,6 +471,11 @@ func getKosServices(participants []string, monitoring bool, local bool, lean boo return services } +// relayerServicePrefix names the per-participant private-relayer services +// ("relayer-a", ...) — shared by the service map, the build attach, and the +// hub-less drop list. +const relayerServicePrefix = "relayer-" + func getRelayerServices(participants []string, monitoring bool, local bool, srcs *Sources) map[string]*Service { services := make(map[string]*Service) otelSdkDisabled := "true" @@ -484,7 +489,7 @@ func getRelayerServices(participants []string, monitoring bool, local bool, srcs portsDebug := 4010 + i participantUpper := strings.ToUpper(p) - serviceName := "relayer-" + p + serviceName := relayerServicePrefix + p envFile := fmt.Sprintf("%s/.%s.env", relayerPathV3, participantUpper) env := []string{ @@ -541,7 +546,7 @@ func getRelayerServices(participants []string, monitoring bool, local bool, srcs // From-source build (--local): attach to the first participant's service // only; siblings reuse the tag the build produces (same pattern as kos). if len(participants) > 0 { - attachBuild(services["relayer-"+participants[0]], srcs, "relayer", "relayer") + attachBuild(services[relayerServicePrefix+participants[0]], srcs, "relayer", "relayer") } return services } @@ -1400,7 +1405,7 @@ func applyNoHub(compose *DockerCompose, participants []string) { "audit-explorer", } for _, p := range participants { - drop = append(drop, "relayer-"+p) + drop = append(drop, relayerServicePrefix+p) } for _, name := range drop { delete(compose.Services, name) diff --git a/internal/stacks/publicchainkey_test.go b/internal/stacks/publicchainkey_test.go index 6c94509..3af0c99 100644 --- a/internal/stacks/publicchainkey_test.go +++ b/internal/stacks/publicchainkey_test.go @@ -140,98 +140,57 @@ func TestResolvePublicChainKey(t *testing.T) { func TestCheckPublicChainKey(t *testing.T) { key := strings.Repeat("ab", 32) + keyLine := " - " + docker.PublicChainKeyComposeEnv + "\n" - t.Run("no compose file passes", func(t *testing.T) { - t.Chdir(t.TempDir()) - clearKeyEnv(t) - if err := CheckPublicChainKey(); err != nil { - t.Errorf("expected nil, got %v", err) - } - }) - - t.Run("local stack compose passes without key", func(t *testing.T) { - t.Chdir(t.TempDir()) - writeCompose(t, "services: {}\n# no public chain key line\n") - clearKeyEnv(t) - if err := CheckPublicChainKey(); err != nil { - t.Errorf("expected nil, got %v", err) - } - }) - - t.Run("testnet compose without any key fails with funding pointer", func(t *testing.T) { - t.Chdir(t.TempDir()) - writeCompose(t, "environment:\n - "+docker.PublicChainKeyComposeEnv+"\n") - clearKeyEnv(t) - err := CheckPublicChainKey() - if err == nil { - t.Fatal("expected an error") - } - if !strings.Contains(err.Error(), docker.FundingURL) { - t.Errorf("error should point at the funding page, got: %v", err) - } - }) - - t.Run("key in env passes", func(t *testing.T) { - t.Chdir(t.TempDir()) - writeCompose(t, " - "+docker.PublicChainKeyComposeEnv+"\n") - clearKeyEnv(t) - t.Setenv(publicChainKeyVar, key) - if err := CheckPublicChainKey(); err != nil { - t.Errorf("expected nil, got %v", err) - } - }) - - t.Run("key in stack .env passes", func(t *testing.T) { - t.Chdir(t.TempDir()) - writeCompose(t, " - "+docker.PublicChainKeyComposeEnv+"\n") - writeEnvFile(t, publicChainKeyVar+"="+key+"\n") - clearKeyEnv(t) - if err := CheckPublicChainKey(); err != nil { - t.Errorf("expected nil, got %v", err) - } - }) - - t.Run("DEMO alias in stack .env passes (compose reads it too)", func(t *testing.T) { - t.Chdir(t.TempDir()) - writeCompose(t, " - "+docker.PublicChainKeyComposeEnv+"\n") - writeEnvFile(t, demoPublicChainKeyVar+"="+key+"\n") - clearKeyEnv(t) - if err := CheckPublicChainKey(); err != nil { - t.Errorf("expected nil, got %v", err) - } - }) - - t.Run("set-but-empty env var fails even with .env key", func(t *testing.T) { - t.Chdir(t.TempDir()) - writeCompose(t, " - "+docker.PublicChainKeyComposeEnv+"\n") - writeEnvFile(t, publicChainKeyVar+"="+key+"\n") - clearKeyEnv(t) - t.Setenv(publicChainKeyVar, "") - err := CheckPublicChainKey() - if err == nil || !strings.Contains(err.Error(), "EMPTY") { - t.Errorf("expected set-but-empty error, got %v", err) - } - }) - - t.Run("malformed key in .env fails with format error", func(t *testing.T) { - t.Chdir(t.TempDir()) - writeCompose(t, " - "+docker.PublicChainKeyComposeEnv+"\n") - writeEnvFile(t, publicChainKeyVar+"=nothex\n") - clearKeyEnv(t) - err := CheckPublicChainKey() - if err == nil || !strings.Contains(err.Error(), "invalid") { - t.Errorf("expected format error, got %v", err) - } - }) - - t.Run("pre-OSS compose with embedded default is left alone", func(t *testing.T) { - t.Chdir(t.TempDir()) - writeCompose(t, " - "+legacyKeyEnvPrefix+"somelegacydefault}}\n") - clearKeyEnv(t) - if err := CheckPublicChainKey(); err != nil { - t.Errorf("expected nil for legacy compose, got %v", err) - } - }) + tests := []struct { + name string + compose string // compose file content; "" = no docker-compose.yaml + envFile string // stack .env content; "" = no .env + env map[string]string // process env to set (empty value = set-but-empty) + wantErr string // substring the error must contain; "" = expect success + }{ + {name: "no compose file passes"}, + {name: "local stack compose passes without key", + compose: "services: {}\n# no public chain key line\n"}, + {name: "testnet compose without any key fails with funding pointer", + compose: "environment:\n" + keyLine, wantErr: docker.FundingURL}, + {name: "key in env passes", + compose: keyLine, env: map[string]string{publicChainKeyVar: key}}, + {name: "key in stack .env passes", + compose: keyLine, envFile: publicChainKeyVar + "=" + key + "\n"}, + {name: "DEMO alias in stack .env passes (compose reads it too)", + compose: keyLine, envFile: demoPublicChainKeyVar + "=" + key + "\n"}, + {name: "set-but-empty env var fails even with .env key", + compose: keyLine, envFile: publicChainKeyVar + "=" + key + "\n", + env: map[string]string{publicChainKeyVar: ""}, wantErr: "EMPTY"}, + {name: "malformed key in .env fails with format error", + compose: keyLine, envFile: publicChainKeyVar + "=nothex\n", wantErr: "invalid"}, + {name: "pre-OSS compose with embedded default is left alone", + compose: " - " + legacyKeyEnvPrefix + "somelegacydefault}}\n"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Chdir(t.TempDir()) + clearKeyEnv(t) + if tc.compose != "" { + writeCompose(t, tc.compose) + } + if tc.envFile != "" { + writeEnvFile(t, tc.envFile) + } + for k, v := range tc.env { + t.Setenv(k, v) + } + err := CheckPublicChainKey() + switch { + case tc.wantErr == "" && err != nil: + t.Errorf("expected nil, got %v", err) + case tc.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErr)): + t.Errorf("expected error containing %q, got %v", tc.wantErr, err) + } + }) + } } func TestPublicChainKeyGuardApplies(t *testing.T) { From 5971dbe9d57494196634c666758be31a3d2b8ad4 Mon Sep 17 00:00:00 2001 From: Nuno Date: Fri, 21 Aug 2026 19:22:48 +0100 Subject: [PATCH 7/7] refactor: extract key-check case runner to satisfy cognitive-complexity gate --- internal/stacks/publicchainkey_test.go | 63 +++++++++++++++----------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/internal/stacks/publicchainkey_test.go b/internal/stacks/publicchainkey_test.go index 3af0c99..8ee96c9 100644 --- a/internal/stacks/publicchainkey_test.go +++ b/internal/stacks/publicchainkey_test.go @@ -138,17 +138,45 @@ func TestResolvePublicChainKey(t *testing.T) { }) } +// keyCheckCase is one CheckPublicChainKey scenario: the on-disk stack state +// (compose + .env), the process env, and the expected outcome. +type keyCheckCase struct { + name string + compose string // compose file content; "" = no docker-compose.yaml + envFile string // stack .env content; "" = no .env + env map[string]string // process env to set (empty value = set-but-empty) + wantErr string // substring the error must contain; "" = expect success +} + +func runKeyCheckCase(t *testing.T, tc keyCheckCase) { + t.Chdir(t.TempDir()) + clearKeyEnv(t) + if tc.compose != "" { + writeCompose(t, tc.compose) + } + if tc.envFile != "" { + writeEnvFile(t, tc.envFile) + } + for k, v := range tc.env { + t.Setenv(k, v) + } + err := CheckPublicChainKey() + if tc.wantErr == "" { + if err != nil { + t.Errorf("expected nil, got %v", err) + } + return + } + if err == nil || !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("expected error containing %q, got %v", tc.wantErr, err) + } +} + func TestCheckPublicChainKey(t *testing.T) { key := strings.Repeat("ab", 32) keyLine := " - " + docker.PublicChainKeyComposeEnv + "\n" - tests := []struct { - name string - compose string // compose file content; "" = no docker-compose.yaml - envFile string // stack .env content; "" = no .env - env map[string]string // process env to set (empty value = set-but-empty) - wantErr string // substring the error must contain; "" = expect success - }{ + tests := []keyCheckCase{ {name: "no compose file passes"}, {name: "local stack compose passes without key", compose: "services: {}\n# no public chain key line\n"}, @@ -170,26 +198,7 @@ func TestCheckPublicChainKey(t *testing.T) { } for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Chdir(t.TempDir()) - clearKeyEnv(t) - if tc.compose != "" { - writeCompose(t, tc.compose) - } - if tc.envFile != "" { - writeEnvFile(t, tc.envFile) - } - for k, v := range tc.env { - t.Setenv(k, v) - } - err := CheckPublicChainKey() - switch { - case tc.wantErr == "" && err != nil: - t.Errorf("expected nil, got %v", err) - case tc.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErr)): - t.Errorf("expected error containing %q, got %v", tc.wantErr, err) - } - }) + t.Run(tc.name, func(t *testing.T) { runKeyCheckCase(t, tc) }) } }