From 7e8f1e56f9c504573cdf704530423d05ab94d1a9 Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Mon, 23 Mar 2026 14:00:20 +0000 Subject: [PATCH 1/2] add TOML config template and localnet env script for Rust launcher --- .../tee/scripts/node.conf.localnet.toml.tpl | 58 +++++++++++++++++++ localnet/tee/scripts/set-localnet-env.sh | 31 ++++++++++ 2 files changed, 89 insertions(+) create mode 100644 localnet/tee/scripts/node.conf.localnet.toml.tpl create mode 100644 localnet/tee/scripts/set-localnet-env.sh diff --git a/localnet/tee/scripts/node.conf.localnet.toml.tpl b/localnet/tee/scripts/node.conf.localnet.toml.tpl new file mode 100644 index 000000000..292b628d2 --- /dev/null +++ b/localnet/tee/scripts/node.conf.localnet.toml.tpl @@ -0,0 +1,58 @@ +[launcher_config] +image_tags = ["${MPC_IMAGE_TAGS}"] +image_name = "${MPC_IMAGE_NAME}" +registry = "${MPC_REGISTRY}" +rpc_request_timeout_secs = 10 +rpc_request_interval_secs = 1 +rpc_max_attempts = 20 +port_mappings = [ +${PORTS_TOML}] + +[mpc_node_config] +home_dir = "/data" + +[mpc_node_config.log] +format = "plain" +filter = "info" + +[mpc_node_config.near_init] +chain_id = "mpc-localnet" +boot_nodes = "${NEAR_BOOT_NODES}" +genesis_path = "/app/localnet-genesis.json" +download_genesis = false + +[mpc_node_config.secrets] +secret_store_key_hex = "${MPC_SECRET_STORE_KEY}" +backup_encryption_key_hex = "0000000000000000000000000000000000000000000000000000000000000000" + +[mpc_node_config.node] +my_near_account_id = "${MPC_ACCOUNT_ID}" +near_responder_account_id = "${MPC_ACCOUNT_ID}" +number_of_responder_keys = 1 +web_ui = "0.0.0.0:8080" +migration_web_ui = "0.0.0.0:8078" +cores = 4 + +[mpc_node_config.node.indexer] +validate_genesis = false +sync_mode = "Latest" +concurrency = 1 +mpc_contract_id = "${MPC_CONTRACT_ID}" +finality = "optimistic" + +[mpc_node_config.node.triple] +concurrency = 2 +desired_triples_to_buffer = 128 +timeout_sec = 60 +parallel_triple_generation_stagger_time_sec = 1 + +[mpc_node_config.node.presignature] +concurrency = 4 +desired_presignatures_to_buffer = 64 +timeout_sec = 60 + +[mpc_node_config.node.signature] +timeout_sec = 60 + +[mpc_node_config.node.ckd] +timeout_sec = 60 diff --git a/localnet/tee/scripts/set-localnet-env.sh b/localnet/tee/scripts/set-localnet-env.sh new file mode 100644 index 000000000..176098bab --- /dev/null +++ b/localnet/tee/scripts/set-localnet-env.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Source this file before running deploy-tee-localnet.sh: +# source localnet/tee/scripts/set-localnet-env.sh + +export HOST_PROFILE=alice +export MODE=localnet +export MPC_NETWORK_BASE_NAME=mpc-local +export REUSE_NETWORK_NAME=mpc-local +export N=2 + +export MACHINE_IP=51.68.219.1 +export BASE_PATH=/mnt/data/barak/dstack +export VMM_RPC=http://127.0.0.1:10000 + +export MPC_IMAGE_NAME=nearone/mpc-node +export MPC_IMAGE_TAGS=main-9515e18 +export MPC_REGISTRY=registry.hub.docker.com + +export NEAR_NETWORK_CONFIG=mpc-localnet +export NEAR_RPC_URL=http://127.0.0.1:3030 +export ACCOUNT_SUFFIX=.test.near + +export FUNDER_ACCOUNT=test.near +export FUNDER_PRIVATE_KEY="$(jq -r '.secret_key' ~/.near/mpc-localnet/validator_key.json)" + +export MAX_NODES_TO_FUND=2 + +export NEAR_TX_SLEEP_SEC=1 +export NEAR_RETRY_SLEEP_SEC=2 + +export RESUME=0 From 45278eea76d14c912ec2e2805a2cabf93080a252 Mon Sep 17 00:00:00 2001 From: Barakeinav1 Date: Mon, 23 Mar 2026 14:12:14 +0000 Subject: [PATCH 2/2] move Rust launcher scripts to rust-launcher/ subfolder --- .../rust-launcher/deploy-tee-localnet.sh | 1656 +++++++++++++++++ .../how-to-run-localnet-tee-setup-script.md | 194 ++ .../node.conf.localnet.toml.tpl | 0 .../{ => rust-launcher}/set-localnet-env.sh | 0 .../tee/scripts/rust-launcher/single-node.sh | 295 +++ 5 files changed, 2145 insertions(+) create mode 100644 localnet/tee/scripts/rust-launcher/deploy-tee-localnet.sh create mode 100644 localnet/tee/scripts/rust-launcher/how-to-run-localnet-tee-setup-script.md rename localnet/tee/scripts/{ => rust-launcher}/node.conf.localnet.toml.tpl (100%) rename localnet/tee/scripts/{ => rust-launcher}/set-localnet-env.sh (100%) create mode 100644 localnet/tee/scripts/rust-launcher/single-node.sh diff --git a/localnet/tee/scripts/rust-launcher/deploy-tee-localnet.sh b/localnet/tee/scripts/rust-launcher/deploy-tee-localnet.sh new file mode 100644 index 000000000..9223e9e9a --- /dev/null +++ b/localnet/tee/scripts/rust-launcher/deploy-tee-localnet.sh @@ -0,0 +1,1656 @@ +#!/usr/bin/env bash +set -euo pipefail +export NEAR_CLI_DISABLE_SPINNER=1 + +# NEAR network selection +# - For testnet: NEAR_NETWORK_CONFIG=testnet (default) +# - For localnet: export NEAR_NETWORK_CONFIG=mpc-localnet (matches your near-cli config name) +NEAR_NETWORK_CONFIG="${NEAR_NETWORK_CONFIG:-testnet}" +# Used by curl-based RPC helpers (balance/bootnodes). Override for localnet if needed. +NEAR_RPC_URL="${NEAR_RPC_URL:-$NEAR_RPC_URL}" +# MPC environment label used by templates / node config +# Default: "testnet" when NEAR_NETWORK_CONFIG=testnet, otherwise "localnet" +MPC_ENV="${MPC_ENV:-mpc-localnet}" + + + + +### ========================= +### Fully-automated, resumable MPC testnet TEE deploy (with subaccounts) +### - Avoids faucet 429 by allowing a funded FUNDER_ACCOUNT to create/top-up ROOT +### - Subaccounts are ALWAYS created by ROOT (required by NEAR permission model) +### - Contract created as mpc., nodes as node{i}. +### - Default funding supports contract (~16 NEAR) + up to 10 nodes (0.3 NEAR ea) + buffer +### - Resume logic + per-phase ENTER prompts +### - NEW: scale/extend network with vote_new_parameters (add nodes) +### +### NOTE on RESUME: +### - RESUME=1 is intended for reruns: it reuses existing artifacts and "auto" phase selection. +### - RESUME=0 primarily affects auto phase selection; artifacts may still exist on disk and +### some steps may still skip based on those artifacts unless FORCE_* is set. +### ========================= + +### Required inputs +: "${MPC_NETWORK_BASE_NAME:?Must set MPC_NETWORK_BASE_NAME (e.g. export MPC_NETWORK_BASE_NAME=barak-test)}" +: "${N:?Must set N (e.g. export N=10)}" +: "${BASE_PATH:?Must set BASE_PATH to dstack base path (contains vmm/src/vmm-cli.py)}" +: "${MACHINE_IP:?Must set MACHINE_IP (external IP for localnet node comms)}" + +: "${MPC_IMAGE_TAGS:?Must set MPC_IMAGE_TAGS (e.g. export MPC_IMAGE_TAGS=3.3.0)}" + +# If set, use this funded testnet account instead of faucet to create/top-up the ROOT account. +# Example: export FUNDER_ACCOUNT=barak_tee_test1.testnet +FUNDER_ACCOUNT="${FUNDER_ACCOUNT:-}" +# Optional: private key for FUNDER_ACCOUNT (useful for localnet where funder may not be in keychain) +FUNDER_PRIVATE_KEY="${FUNDER_PRIVATE_KEY:-}" + +# How much balance to ensure on ROOT (used for creating contract+nodes subaccounts) +# Default supports ~16 NEAR contract + 10 * 0.3 NEAR nodes + ~1 NEAR buffer => 20 NEAR +ROOT_INITIAL_BALANCE="${ROOT_INITIAL_BALANCE:-20 NEAR}" + +### Optional controls +ACCOUNT_MODE="${ACCOUNT_MODE:-subaccounts}" # subaccounts|faucet (faucet is fallback only) + +# Initial balances (for subaccounts mode) +CONTRACT_INITIAL_BALANCE="${CONTRACT_INITIAL_BALANCE:-20 NEAR}" +NODE_INITIAL_BALANCE="${NODE_INITIAL_BALANCE:-3.0 NEAR}" + +# How many nodes to fund for, even if N is smaller (so you can scale later without re-funding root) +MAX_NODES_TO_FUND="${MAX_NODES_TO_FUND:-10}" + +# Faucet retry/backoff (for root creation if FUNDER_ACCOUNT is not set) +FAUCET_MAX_RETRIES="${FAUCET_MAX_RETRIES:-8}" +FAUCET_BACKOFF_BASE_SEC="${FAUCET_BACKOFF_BASE_SEC:-10}" + +# Resume behavior +RESUME="${RESUME:-1}" +FORCE_REDEPLOY="${FORCE_REDEPLOY:-0}" +FORCE_RECOLLECT="${FORCE_RECOLLECT:-0}" +FORCE_REINIT_ARGS="${FORCE_REINIT_ARGS:-0}" + +# Phase gating +START_FROM_PHASE="${START_FROM_PHASE:-auto}" # auto recommended +STOP_AFTER_PHASE="${STOP_AFTER_PHASE:-}" + +# Pause between phases +NO_PAUSE="${NO_PAUSE:-0}" + +# --- Scale / add nodes controls --- +# Set ADD_NODES>0 (or set NEW_TOTAL_N) and run phase near_vote_new_parameters (add nodes) +ADD_NODES="${ADD_NODES:-0}" # how many NEW nodes to add +NEW_TOTAL_N="${NEW_TOTAL_N:-}" # optional: absolute total count after scaling +NEW_THRESHOLD_OVERRIDE="${NEW_THRESHOLD_OVERRIDE:-}" # optional: override threshold for new proposal + +# If set, reuse existing network name (and NEAR accounts) +if [ -n "${REUSE_NETWORK_NAME:-}" ]; then + RAND_SUFFIX="(reused)" + MPC_NETWORK_NAME="${REUSE_NETWORK_NAME}" +else + RAND_SUFFIX="$(printf '%04x' $((RANDOM % 65536)))" + MPC_NETWORK_NAME="${MPC_NETWORK_BASE_NAME}-${RAND_SUFFIX}" +fi + +### Constants / defaults +# Host profile: alice | bob +HOST_PROFILE="${HOST_PROFILE:-bob}" + +case "$HOST_PROFILE" in + alice) + IP_PREFIX="51.68.219." + IP_START_OCTET=1 + ;; + bob) + IP_PREFIX="5.196.36." + IP_START_OCTET=113 + ;; + *) + err "Unknown HOST_PROFILE: $HOST_PROFILE (supported: alice | bob)" + exit 1 + ;; +esac + +# Optional per-node IP override (format: "5=5.196.36.113 6=5.196.36.114 ...") +NODE_IP_OVERRIDES="${NODE_IP_OVERRIDES:-}" + +SSH_BASE=1220 +AGENT_BASE=18090 +PUBLIC_DATA_BASE=18081 +LOCAL_DEBUG_BASE=3031 + +STATE_SYNC_PORT=24567 +MAIN_PORT=80 +FUTURE_PORT=13001 +FUTURE_BASE_PORT="${FUTURE_BASE_PORT:-13001}" # host-side per-node future/N2N port base +future_port_for_i() { echo $((FUTURE_BASE_PORT + $1)); } + +INTERNAL_PUBLIC_DEBUG_PORT=8080 +INTERNAL_LOCAL_DEBUG_PORT=3030 +INTERNAL_STATE_SYNC_PORT=24567 +INTERNAL_MAIN_PORT=80 +INTERNAL_FUTURE_PORT=13001 + +OS_IMAGE="${OS_IMAGE:-dstack-dev-0.5.4}" +SEALING_KEY_TYPE="${SEALING_KEY_TYPE:-SGX}" +VMM_RPC="${VMM_RPC:-http://127.0.0.1:10000}" + +# Repo-relative paths (assumes you're running from repo root) +REPO_ROOT="$(pwd)" +TEE_LAUNCHER_DIR="$REPO_ROOT/deployment/cvm-deployment" +COMPOSE_YAML="$TEE_LAUNCHER_DIR/launcher_docker_compose.yaml" +ADD_DOMAIN_JSON="$REPO_ROOT/docs/localnet/args/add_domain.json" + +MODE="${MODE:-testnet}" # testnet|localnet + +# templates live here (UPDATED for move to localnet/tee/scripts) +ENV_TPL="$REPO_ROOT/localnet/tee/scripts/node.env.tpl" +if [ "$MODE" = "localnet" ]; then + CONF_TPL="$REPO_ROOT/localnet/tee/scripts/node.conf.localnet.toml.tpl" +else + CONF_TPL="$REPO_ROOT/localnet/tee/scripts/node.conf.tpl" +fi + +# Convert comma-separated "host:container" port string to TOML inline table array entries. +# E.g. "8080:8080,24566:24566" -> " { host = 8080, container = 8080 },\n..." +ports_to_toml() { + local ports="$1" result="" + IFS=',' read -ra pairs <<< "$ports" + for pair in "${pairs[@]}"; do + local host_port="${pair%%:*}" + local container_port="${pair##*:}" + result+=" { host =$host_port, container =$container_port }, +" + done + echo -n "$result" +} + +WORKDIR="/tmp/$USER/mpc_testnet_scale/$MPC_NETWORK_NAME" +mkdir -p "$WORKDIR" + +# Derived accounts +ACCOUNT_SUFFIX="${ACCOUNT_SUFFIX:-.testnet}" # localnet example: ".test.near" +ROOT_ACCOUNT="${MPC_NETWORK_NAME}${ACCOUNT_SUFFIX}" + +# Subaccount naming (REQUIRED for subaccounts mode) +MPC_CONTRACT_ACCOUNT="${MPC_CONTRACT_ACCOUNT:-mpc.${ROOT_ACCOUNT}}" +node_account_for_i() { echo "node$1.${ROOT_ACCOUNT}"; } + +NODE_RANGE_START="${NODE_RANGE_START:-0}" +NODE_RANGE_END="${NODE_RANGE_END:-$((N-1))}" + +# Artifact paths +KEYS_JSON="$WORKDIR/keys.json" +INIT_ARGS_JSON="$WORKDIR/init_args.json" +KEYS_NEW_JSON="$WORKDIR/keys_new.json" +VOTE_PARAMS_JSON="$WORKDIR/vote_new_parameters.json" + +# Retry/sleep knobs (FIX #1) +NEAR_TX_SLEEP_SEC="${NEAR_TX_SLEEP_SEC:-3}" + +near_sleep() { + local reason="${1:-after NEAR tx}" + log "Sleeping ${NEAR_TX_SLEEP_SEC}s (${reason})" + sleep "$NEAR_TX_SLEEP_SEC" +} + +# ---------- logging ---------- +log() { echo -e "\033[1;34m[INFO]\033[0m $*"; } +warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; } +err() { echo -e "\033[1;31m[ERROR]\033[0m $*"; } + +pause_phase() { + local name="$1" + if [ "$NO_PAUSE" = "1" ]; then + log "NO_PAUSE=1 -> continuing automatically (phase: $name)" + return 0 + fi + echo + echo "------------------------------------------------------------" + echo "Phase: $name" + echo "Network: $MPC_NETWORK_NAME" + echo "Workdir: $WORKDIR" + echo "Account mode: $ACCOUNT_MODE" + echo "ROOT_ACCOUNT: $ROOT_ACCOUNT" + echo "CONTRACT_ACCOUNT: $MPC_CONTRACT_ACCOUNT" + echo "FUNDER_ACCOUNT: ${FUNDER_ACCOUNT:-}" + echo "------------------------------------------------------------" + read -r -p "Press ENTER to continue (or Ctrl+C to abort)..." _ + echo +} + +# ---------- helpers ---------- +ceil_2n_3() { local n="$1"; echo $(( (2*n + 2) / 3 )); } + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { err "Missing required command: $1"; exit 1; } +} + +ip_for_i() { + local i="$1" + if [ -n "$NODE_IP_OVERRIDES" ]; then + local ip + ip="$(echo " $NODE_IP_OVERRIDES " | sed -n "s/.*[[:space:]]${i}=\([^[:space:]]*\).*/\1/p")" + if [ -n "$ip" ]; then + echo "$ip" + return 0 + fi + fi + echo "${IP_PREFIX}$((IP_START_OCTET + i))" +} + +ssh_port_for_i() { echo $((SSH_BASE + $1)); } +agent_port_for_i() { echo $((AGENT_BASE + $1)); } +public_port_for_i() { echo 18082; } # fixed public_data port +local_dbg_port_for_i() { echo $((LOCAL_DEBUG_BASE + $1)); } + +# NOTE: this preflight check assumes the node IPs are configured as /32 addresses on the host +# (common for IP aliasing setups). If your environment uses a different prefix length, adjust +# the grep pattern accordingly. +host_has_ip() { local ip="$1"; ip addr show | grep -qE "inet ${ip}/32"; } + +port_free() { + local ip="$1" port="$2" + local addrs + addrs="$(ss -H -4 -ltn 2>/dev/null | awk '{print $4}')" + if echo "$addrs" | grep -Eq "0\.0\.0\.0:${port}$|${ip//./\\.}:${port}$"; then + return 1 + fi + return 0 +} + +file_nonempty() { local p="$1"; [ -f "$p" ] && [ -s "$p" ]; } + +# keys.json is a JSON array; treat [] as "empty" even though the file is non-empty. +json_array_has_entries() { + local p="$1" + [ -f "$p" ] || return 1 + set +e + local n + n="$(jq 'length' "$p" 2>/dev/null)" + local rc=$? + set -e + [ $rc -eq 0 ] && [[ "$n" =~ ^[0-9]+$ ]] && [ "$n" -gt 0 ] +} + +maybe_stop_after_phase() { + local phase="$1" + if [ -n "$STOP_AFTER_PHASE" ] && [ "$STOP_AFTER_PHASE" = "$phase" ]; then + warn "STOP_AFTER_PHASE=$STOP_AFTER_PHASE requested. Stopping now." + exit 0 + fi +} + +# ---------- phase gating ---------- +phase_rank() { + case "$1" in + preflight) echo 10 ;; + render) echo 20 ;; + near_accounts) echo 30 ;; + near_nodes) echo 40 ;; + near_contract) echo 50 ;; + deploy) echo 60 ;; + collect) echo 70 ;; + init_args) echo 75 ;; + near_keys) echo 80 ;; + near_init) echo 90 ;; + near_vote_hash) echo 93 ;; + near_vote_launcher_hash) echo 94 ;; + near_vote_measurement) echo 95 ;; + near_vote_domain) echo 96 ;; + near_vote_new_params) echo 98 ;; + near_vote_new_params_votes) echo 99 ;; + + auto) echo 0 ;; + *) err "Unknown phase name: $1"; exit 1 ;; + esac +} + +should_run_from_start() { + local phase="$1" + local start="$START_FROM_PHASE" + if [ "$start" = "auto" ]; then + return 0 + fi + local pr sr + pr="$(phase_rank "$phase")" + sr="$(phase_rank "$start")" + [ "$pr" -ge "$sr" ] +} + +compute_auto_start_phase() { + if [ "$START_FROM_PHASE" != "auto" ]; then + echo "$START_FROM_PHASE" + return 0 + fi + if [ "$RESUME" != "1" ]; then + echo "preflight" + return 0 + fi + if file_nonempty "$INIT_ARGS_JSON"; then + echo "near_keys" + return 0 + fi + if json_array_has_entries "$KEYS_JSON"; then + echo "init_args" + return 0 + fi + echo "preflight" +} + +### ========================= +### BOOTNODES (DEDUP BY ADDR) +### ========================= +fetch_bootnodes() { + # Bootnodes + # - Override via NEAR_BOOT_NODES_OVERRIDE (recommended for fully offline setups) + # - Otherwise, query the configured RPC (NEAR_RPC_URL) for network_info + if [ -n "${NEAR_BOOT_NODES_OVERRIDE:-}" ]; then + echo "$NEAR_BOOT_NODES_OVERRIDE" + return 0 + fi + + # Best-effort: if RPC doesn't support network_info or returns empty, return empty string + set +e + local out + out="$(curl -s -X POST "$NEAR_RPC_URL" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc": "2.0", "method": "network_info", "params": [], "id": "dontcare"}' 2>/dev/null)" + local rc=$? + set -e + if [ $rc -ne 0 ] || [ -z "$out" ]; then + echo "" + return 0 + fi + echo "$out" \ + | jq -r '.result.active_peers[]? | "\(.id)@\(.addr)"' 2>/dev/null \ + | awk -F'@' '!seen[$2]++ {print $0}' \ + | paste -sd',' - +} + + +### ========================= +### NEAR helpers (balance, create, topup) +### ========================= + +is_existing_key_error() { + local out="$1" + # This matches the exact near-cli-rs message + echo "$out" | grep -Eq "Public key is already used for an existing account ID <" +} + +near_add_key_skip_if_exists() { + # Usage: near_add_key_skip_if_exists "" "" "