Validator install guide
Deploy the 2QUIC TEE into your own AWS account with validator-cli fetch-release and deploy.sh.
This page is the manual command reference for installing the TEE stack
(Nitro Enclave + host-proxy) into your own AWS account from the open-source
repo staked-quic-connection-provider. Everything runs under your AWS
account and your credentials. The marketplace never touches your
infrastructure.
The recommended path is the web onboarding funnel at /validator
(Identify, Provision TEE, Register, Live). It prefills two command blocks:
a clone + build + deploy install block (which uses the from-source
build.sh path rather than the operator fetch-release path in Step 2)
and the validator-cli register command with the one-time
--onboard-token that attributes the listing to your web account. See
Validator onboarding. Every other command
on this page is manual only.
Prerequisites
One-time AWS account preparation is covered by deploy/PREREQUISITES.md in
the staked-quic-connection-provider repo. Budget 30 to 45 minutes for it:
MFA on the root account, a dedicated IAM deploy user with a minimal policy,
and a region choice. The playbook defaults to us-east-1 (the cheapest).
Nitro-capable instance families are available in every major commercial
region.
Local tools, from the same document:
| Tool | Min version | Needed by |
|---|---|---|
| AWS CLI | v2 | everyone |
| Terraform | 1.6 | everyone |
| Rust | 1.92 (pinned by rust-toolchain.toml) | everyone, builds validator-cli |
| openssl | 1.1+ | everyone |
| jq | 1.6+ | everyone |
| grpcurl | 1.9+ | everyone |
| Docker | 24+ | developers building from source (build.sh), and smoke-test.sh |
deploy.sh additionally expects python3, curl, and xxd, which are
preinstalled on most systems. You also need your validator identity keypair
as a 64-byte solana-keygen JSON file on the machine you deploy from.
Step 1: build validator-cli
git clone https://github.com/nodexpert-labs/staked-quic-connection-provider.git
cd staked-quic-connection-provider
cargo install --path validator-cli --locked
Rust is required on the operator workstation solely for this build. The CLI carries the embedded K5 release trust root (the key that signs releases), so it must not be downloaded over the very channel it verifies. The deployed artefacts themselves are never built by operators.
The CLI has exactly five subcommands: fetch-release, register,
set-payout-wallet, security, and emergency-stop.
Step 2: stage the release artefacts
deploy.sh never builds anything. It consumes pre-staged artefacts from
deploy/artefacts/ and fails fast if they are missing. There are two ways
to stage them.
Operators fetch the signed release:
validator-cli fetch-release --version 1.2.3
This downloads the release from the GitHub Releases base (default
https://github.com/nodexpert-labs/staked-quic-connection-provider/releases,
override with --releases-url), K5-verifies the signed EIF attestation,
refuses a version mismatch, and sha384-verifies enclave.eif,
host-proxy, and provision before placing them in deploy/artefacts/.
It also writes .pcr-values.json (the PCR0/1/2 enclave measurements from
the signed attestation) and .release-version.json (which enables the
revocation kill-switch poller on the host).
Developers building from source run ./deploy/scripts/build.sh
instead. It builds the binaries and the EIF inside an Amazon Linux 2023
Docker container (Linux x86_64 only, use WSL2 or a VM on macOS/Windows) and
captures the PCRs from nitro-cli build-enclave. Locally built artefacts
carry no .release-version.json, so the subsequent deploy needs the
test-only --dev-no-pollers flag.
Step 3: export the marketplace JWT environment
deploy.sh validates these before touching Terraform and exits 1 if any of
the three required variables is missing. They configure the host-proxy to
verify the short-lived ES256 lease JWTs the marketplace mints for traders.
| Variable | Required | What it is |
|---|---|---|
MARKETPLACE_JWT_PUBKEY_PEM | yes | The marketplace's ES256 (P-256) JWT verification public key, PEM format |
MARKETPLACE_JWT_AUD | yes | The aud claim the host-proxy accepts. Must equal the endpoint_url you register, byte for byte |
MARKETPLACE_JWT_VAL_PK | yes | Your validator identity pubkey, base58. Matched against the JWT val_pk claim |
MARKETPLACE_JWT_KID | no | kid the marketplace puts in JWT headers. Default mkt-jwt-v1-dev |
MARKETPLACE_API_URL | no | Set to https://api.swqos.dev to enable usage telemetry. The API key file written by register next to your keypair is detected automatically |
export MARKETPLACE_JWT_PUBKEY_PEM="$(cat marketplace-jwt-pubkey.pem)"
export MARKETPLACE_JWT_AUD="https://__PUBLIC_IP__:443"
export MARKETPLACE_JWT_VAL_PK="<your-identity-pubkey-base58>"
MARKETPLACE_JWT_AUD must equal the registered endpoint_url byte for
byte: scheme, host case, port, trailing slash. A mismatch rejects every
newly minted lease JWT with PERMISSION_DENIED "wrong audience". If you
ever change the listing's endpoint_url, update this value on the host in
lockstep. The literal __PUBLIC_IP__ placeholder is replaced with the
allocated Elastic IP after terraform apply, which is useful when no DNS
name fronts the endpoint.
The test-only --insecure-no-auth flag skips this block and deploys with
an empty [jwt] config. Never use it for a listing that will take real
leases.
Step 4: run deploy.sh
./deploy/scripts/deploy.sh \
--identity-keypair /path/to/validator-keypair.json \
--region us-east-1
| Flag | Default | Meaning |
|---|---|---|
--identity-keypair <path> | required | 64-byte Solana keypair JSON |
--region <region> | us-east-1 | AWS region to deploy into |
--allowed-cidr <CIDR> | detected operator IP/32 | Ingress allowlist on the gRPC 443 security group rule |
--plan | off | terraform plan only, then stop (no apply) |
--insecure-no-auth | off | Skip the JWT config (TEST ONLY) |
--dev-no-pollers | off | Disable the revocation poller (TEST ONLY, required for locally built artefacts) |
The script fails fast if a Terraform state already contains resources (run
destroy.sh first). A --plan run is a complete pre-flight: tools, AWS
credentials, staged artefacts, and JWT environment are all checked up
front.
A full run takes roughly 10 to 15 minutes and does, in order:
- Validates the keypair and derives your identity pubkey.
- Fails fast on existing state or missing artefacts.
- Detects your public IP for the security-group allowlist.
- Picks the cheapest Nitro-capable instance type available in the region.
Candidates in order:
c6a.xlarge,c5.xlarge,c6i.xlarge,c7i.xlarge,m6a.xlarge,m6i.xlarge. There is no tier or instance flag, selection is automatic. - Runs
terraform apply. - Waits for the SSM agent to come online.
- KMS-encrypts the validator seed and installs the secrets: the seed
ciphertext travels in the SSM command, the rendered
config.tomlgoes via the SSE S3 bucket (deleted afterwards), and the TLS keypair is generated on the host. Only the public cert is echoed back todeploy/tls/cert.pem. - Starts the enclave and host-proxy, then runs
provision, which KMS-decrypts the seed inside the enclave. - Verifies gRPC
Health: the enclave reportsprovisioned: trueand the reportedpubkey_sha256matches the SHA-256 of your identity pubkey. A mismatch aborts. - Prints the client config: endpoint, port, auth, CA cert path, TEE pubkey.
What Terraform actually provisions
- A VPC with one public subnet, an internet gateway, and a route table.
- A security group allowing gRPC 443 from
--allowed-cidronly. - One Nitro-capable EC2 instance plus an Elastic IP.
- A KMS key and alias whose decrypt policy is attestation-gated (PCR-bound to the staged enclave measurements).
- An S3 artefacts bucket (SSE, versioned, public access blocked).
- An IAM role, instance profile, and policies, plus the SSM attachment.
There is no NAT gateway, no load balancer, no VPC endpoints, and no CloudWatch log groups. Host and proxy logs live on the instance via systemd/journald, reachable over SSM.
Step 5: register the listing
The endpoint printed by deploy.sh becomes the endpoint_url of your
listing, and it must equal the MARKETPLACE_JWT_AUD you exported (after
__PUBLIC_IP__ substitution). Register with:
validator-cli register \
--identity-keypair /path/to/validator-keypair.json \
--endpoint-url https://<your-eip>:443 \
--price 2.5 \
--bio "Your public listing bio" \
--region us-east-1
The full flag reference (payout wallet, shared offering, onboarding token,
dry run) is on the onboarding page. Two
things to know now: the minted API key is written once to a 0600 file named
<validator_id>.api-key next to your keypair and never shown again, and
the listing goes live immediately on successful registration.
Step 6: verify
deploy.sh already verified the enclave at step 9. To re-check manually at
any time:
grpcurl -insecure \
-import-path proto -proto signer.proto \
<your-eip>:443 signer.v1.StakedQuicSigner/Health
Expect provisioned: true. The -insecure flag is needed because the TEE
leaf certificate is self-signed with the Elastic IP in the SAN. The
host-proxy does not expose gRPC reflection, so grpcurl must be given the
proto file. Run the command from the root of the repo checkout from
Step 1, which carries proto/signer.proto.
Run the end-to-end smoke test, which sends 50 memo transactions through the full TLS + TEE + QUIC pipeline in staked mode and reports landing rate plus p50/p95 confirm latency:
MARKETPLACE_LEASE_JWT=<a-marketplace-lease-jwt> ./deploy/scripts/smoke-test.sh
It needs Docker, solana, and solana-keygen locally, takes
--rpc-url (default https://api.testnet.solana.com), --tx-count
(default 50), and --trader-keypair (default deploy/trader-keypair.json),
and requires a marketplace lease JWT in MARKETPLACE_LEASE_JWT.
Finally, once registered, the marketplace probe calls
SignCertificateVerify on your TEE every 60 seconds. Watch probe success
and TEE health on your dashboard at /validator.
Re-printing the client config
./deploy/scripts/display-config.sh
Prints the client configuration (endpoint, auth, CA cert path, TEE pubkey)
of the current deployment. Idempotent, safe to run any time after
deploy.sh.
Teardown
./deploy/scripts/destroy.sh # interactive confirmation
./deploy/scripts/destroy.sh --yes # skip the prompt
This runs terraform destroy (EC2, EIP, KMS key, S3 bucket, VPC) and wipes
local secrets (the test seed, deploy/tls/, report files). It preserves
trader-keypair.json, deploy/artefacts/, and terraform.tfvars, so the
next deploy.sh run is fast. Since deploy.sh refuses to run over an
existing state, destroy.sh is also the path to a from-scratch redeploy.
For an in-place version upgrade or rollback, do not destroy. Use
fetch-release plus update.sh as described in the
TEE upgrade runbook. After an EC2 reboot the
enclave comes back unprovisioned by design, see the
incident response runbook for
reprovision.sh.
Next steps
The web funnel, the register flags, and how the listing is attributed to your account.
Exclusive and shared pricing, costs, and break-even math.
In-place upgrades and rollbacks with fetch-release and update.sh.
Reboot recovery, wrong audience, emergency stop.