diff --git a/.gitignore b/.gitignore index 7a3e7b013a..fd16dd28a3 100644 --- a/.gitignore +++ b/.gitignore @@ -201,3 +201,8 @@ dist/ .localnet completions + +## Devnet +# Ignore everything in the nodes/ directory +scripts/devnet/shared +/scripts/devnet/.env diff --git a/scripts/devnet/Dockerfile b/scripts/devnet/Dockerfile new file mode 100644 index 0000000000..e438301416 --- /dev/null +++ b/scripts/devnet/Dockerfile @@ -0,0 +1,20 @@ +# Use Go 1.23 as the base image for building and setting up the environment +FROM golang:1.23 as builder + +# Define a build argument for the base path +ARG BASE_PATH=/mnt/nvme + +# Set the working directory using the base path +WORKDIR ${BASE_PATH} + +# Update package lists and install dependencies (jq) +RUN apt-get update && apt-get install -y jq + +# Clone the regen repository and checkout the specific version (this can be parameterized) +ARG REGEN_VERSION=v5.1.4 +RUN git clone https://github.com/regen-network/regen-ledger.git && \ + cd regen-ledger && \ + git checkout ${REGEN_VERSION} + +# Build the regen binary using the Makefile +RUN cd ${BASE_PATH}/regen-ledger && make build && cp build/regen /usr/local/bin \ No newline at end of file diff --git a/scripts/devnet/README.md b/scripts/devnet/README.md new file mode 100644 index 0000000000..cbb0cb31c6 --- /dev/null +++ b/scripts/devnet/README.md @@ -0,0 +1,94 @@ +# Regen Network DevNet Setup Guide + +This README provides step-by-step instructions for setting up a multi-node Regen Network development network (DevNet) using Docker Compose. This guide aims to help developers quickly spin up a local Regen Network for testing and development purposes. + + +## Prerequisites +Before starting, ensure you have the following installed on your machine: + +* **Docker:** Download and install Docker +* **Docker Compose:** Download and install Docker Compose +* **Git:** Download and install Git +* **jq:** Command-line JSON processor (Install Guide) + +## Usage +```shell +chmod +x setup.sh +./setup.sh +``` +**Note:** The script will clean up any existing data to ensure a fresh start. + +## Interacting with the network +Once the nodes are running, you can interact with them using regen CLI commands. + +For example, to query the status of the first node: + +```shell +regen status --node http://localhost:26657 +``` + +Replace 26657 with the appropriate RPC port if interacting with other nodes (26659, 26661). + + +## File Structure +```text +├── Dockerfile # Defines the Docker image used for the Regen nodes. +├── docker-compose.yml # Docker Compose configuration for the three nodes. +├── entrypoint.sh # Entry script executed inside each Docker container to initialize and start the node. +├── setup.sh # Script to generate keys, write environment variables, and start the network. +└── shared/ # Shared directory for nodes to exchange files (e.g., genesis files, gentxs). + +``` + +## Scripts Overview +### `setup.sh` +This script automates the initial setup: + +1. **Generates Validator Keys:** Creates keys for each node and extracts their addresses and mnemonics. +2. **Writes .env File:** Stores the addresses and mnemonics as environment variables. +3. **Starts Docker Compose:** Brings up the network with all three nodes. + +### `entrypoint.sh` +Executed inside each Docker container, this script: + +1. Initializes the node if it's not already initialized. +2. Imports the validator key using the mnemonic. +3. Generates the gentx file. +4. Coordinates with other nodes to collect gentx files and finalize the genesis file. +5. Configures persistent peers and starts the node. + +#### Key Features: + +**Synchronization Mechanisms:** Ensures nodes wait for each other during initialization. + +### `docker-compose.yml` +Defines the Docker services for the three nodes. + +Services: +* regen-node1 +* regen-node2 +* regen-node3 + + +#### Configurations: + +* **Environment Variables:** Loaded from the .env file. +* **Ports:** Exposes RPC and P2P ports for each node. +* **Volumes:** Mounts the shared directory and entrypoint.sh script. +* **Networks:** All nodes are on the same Docker network for internal communication. +* **.env File**: Automatically generated by setup.sh, this file contains validator addresses and mnemonics for all nodes. +Example Contents: + +``` +REGEN_NODE1_VALIDATOR_ADDRESS=regen1... +REGEN_NODE1_VALIDATOR_MNEMONIC="mnemonic words ..." +REGEN_NODE2_VALIDATOR_ADDRESS=regen1... +REGEN_NODE2_VALIDATOR_MNEMONIC="mnemonic words ..." +REGEN_NODE3_VALIDATOR_ADDRESS=regen1... +REGEN_NODE3_VALIDATOR_MNEMONIC="mnemonic words ..." +``` + +# State Initialization +For state initialization we recommend using a `state_init.sh` script +written by the developer. This script should contain a list of txs using +the regen cli to initialize the desired state before the usage of the tesnet. \ No newline at end of file diff --git a/scripts/devnet/entrypoint.sh b/scripts/devnet/entrypoint.sh new file mode 100644 index 0000000000..be3a613272 --- /dev/null +++ b/scripts/devnet/entrypoint.sh @@ -0,0 +1,190 @@ +#!/bin/bash +set -e + +# Constants +BASE_PATH=${BASE_PATH:-/mnt/nvme} +HOME_DIR=${BASE_PATH}/.regen +SHARED_DIR=${BASE_PATH}/shared +GENTX_DIR="$SHARED_DIR/gentxs" +INITIAL_GENESIS_READY="$SHARED_DIR/initial_genesis_ready" +FINAL_GENESIS_READY="$SHARED_DIR/final_genesis_ready" +CHAIN_ID="regen-devnet" + +# Colors and Emojis +GREEN='\033[0;32m' +NC='\033[0m' +INFO="${GREEN}ℹ️${NC}" +SUCCESS="${GREEN}✅${NC}" +WAIT="${GREEN}⏳${NC}" + +# Helper: Print message with emoji +log() { echo -e "${1} ${2}"; } + +# Ensure shared directories exist +mkdir -p "$GENTX_DIR" + +# Determine the number of nodes +NODE_COUNT="${NODE_COUNT:-3}" +NODE_NAMES=($(for i in $(seq 1 "$NODE_COUNT"); do echo "regen-node$i"; done)) + +# Fetch ports from environment variables +P2P_PORT=${P2P_PORT:-26656} +RPC_PORT=${RPC_PORT:-26657} + +configure_rpc_and_p2p() { + CONFIG_FILE="$HOME_DIR/config/config.toml" + APP_FILE="$HOME_DIR/config/app.toml" + + log "$INFO" "🔍 Verifying configuration of RPC, P2P, and gRPC for ${NODE_NAME}..." + + # Configure RPC in the [rpc] section of config.toml + if [ -f "$CONFIG_FILE" ]; then + log "$INFO" "⚙️ Configuring RPC and P2P in $CONFIG_FILE..." + + # Update RPC address only in the [rpc] section + sed -i "/\[rpc\]/,/^\[.*\]/ s|^laddr *=.*|laddr = \"tcp://0.0.0.0:$RPC_PORT\"|" "$CONFIG_FILE" + + # Update P2P address only in the [p2p] section + sed -i "/\[p2p\]/,/^\[.*\]/ s|^laddr *=.*|laddr = \"tcp://0.0.0.0:$P2P_PORT\"|" "$CONFIG_FILE" + sed -i "/\[p2p\]/,/^\[.*\]/ s|^external_address *=.*|external_address = \"tcp://0.0.0.0:$P2P_PORT\"|" "$CONFIG_FILE" + + log "$SUCCESS" "✅ Configured RPC on $RPC_PORT and P2P on $P2P_PORT in $CONFIG_FILE." + else + log "$INFO" "⚠️ $CONFIG_FILE not found. Skipping P2P and RPC configuration." + fi + + # Configure gRPC and API in app.toml + if [ -f "$APP_FILE" ]; then + log "$INFO" "⚙️ Configuring gRPC and API in $APP_FILE..." + + # Update gRPC address in the [grpc] section + sed -i "/\[grpc\]/,/^\[.*\]/ s|^address *=.*|address = \"localhost:$GRPC_PORT\"|" "$APP_FILE" + + + log "$SUCCESS" "✅ Configured gRPC on $GRPC_PORT and API enabled in $APP_FILE." + else + log "$INFO" "⚠️ $APP_FILE not found. Skipping gRPC and API configuration." + fi +} + + +fetch_environment_variables() { + NODE_ENV_NAME=$(echo "${NODE_NAME^^}" | tr '-' '_') + VALIDATOR_MNEMONIC_VAR="${NODE_ENV_NAME}_VALIDATOR_MNEMONIC" + VALIDATOR_ADDRESS_VAR="${NODE_ENV_NAME}_VALIDATOR_ADDRESS" + + VALIDATOR_MNEMONIC="${!VALIDATOR_MNEMONIC_VAR}" + VALIDATOR_ADDRESS="${!VALIDATOR_ADDRESS_VAR}" + + if [ -z "$VALIDATOR_MNEMONIC" ] || [ -z "$VALIDATOR_ADDRESS" ]; then + log "$WAIT" "Mnemonic or address not found for ${NODE_NAME}!" + exit 1 + fi + log "$SUCCESS" "Fetched mnemonic and address for ${NODE_NAME}." +} + +initialize_node() { + if [ ! -f "$HOME_DIR/config/node_key.json" ]; then + regen init "$NODE_NAME" --chain-id "$CHAIN_ID" --home "$HOME_DIR" + log "$SUCCESS" "Initialized ${NODE_NAME}." + fi +} + +save_node_id() { + NODE_ID=$(regen tendermint show-node-id --home "$HOME_DIR") + echo "$NODE_ID" > "$SHARED_DIR/${NODE_NAME}_id" + log "$SUCCESS" "Saved Node ID for ${NODE_NAME}: $NODE_ID" +} + +add_validator_accounts_to_genesis() { + log "$INFO" "Adding validator accounts to genesis..." + for NODE in "${NODE_NAMES[@]}"; do + NODE_ENV=$(echo "${NODE^^}" | tr '-' '_') + ADDR_VAR="${NODE_ENV}_VALIDATOR_ADDRESS" + ADDRESS="${!ADDR_VAR}" + log "$INFO" "Adding ${NODE}'s address: ${ADDRESS}" + regen add-genesis-account "$ADDRESS" 100000000uregen --home "$HOME_DIR" + done + log "$SUCCESS" "All validator accounts added to genesis." +} + +generate_gentx() { + log "$INFO" "Generating gentx for ${NODE_NAME}..." + echo "$VALIDATOR_MNEMONIC" | regen keys add my_validator --recover --keyring-backend test --home "$HOME_DIR" + regen gentx my_validator 50000000uregen --keyring-backend test --chain-id "$CHAIN_ID" --home "$HOME_DIR" + cp "$HOME_DIR/config/gentx/"*.json "$GENTX_DIR/${NODE_NAME}_gentx.json" + log "$SUCCESS" "Generated gentx for ${NODE_NAME}." +} + +wait_for_gentx_files() { + log "$INFO" "Waiting for all gentx files..." + for NODE in "${NODE_NAMES[@]}"; do + if [ "$NODE" != "$NODE_NAME" ]; then + while [ ! -f "$GENTX_DIR/${NODE}_gentx.json" ]; do + log "$WAIT" "Waiting for gentx from ${NODE}..." + sleep 2 + done + log "$SUCCESS" "Received gentx from ${NODE}." + fi + done +} + +wait_for_initial_genesis() { + log "$INFO" "${NODE_NAME} waiting for initial genesis.json..." + while [ ! -f "$INITIAL_GENESIS_READY" ]; do + sleep 2 + done + cp "$SHARED_DIR/genesis.json" "$HOME_DIR/config/genesis.json" + log "$SUCCESS" "Initial genesis.json received for ${NODE_NAME}." +} + +wait_for_final_genesis() { + log "$INFO" "Waiting for finalized genesis.json..." + while [ ! -f "$FINAL_GENESIS_READY" ]; do + sleep 2 + done + cp "$SHARED_DIR/genesis.json" "$HOME_DIR/config/genesis.json" + log "$SUCCESS" "Finalized genesis.json received for ${NODE_NAME}." +} + +collect_and_finalize_genesis() { + log "$INFO" "Collecting and validating gentx files..." + regen collect-gentxs --gentx-dir "$GENTX_DIR" --home "$HOME_DIR" + regen validate-genesis --home "$HOME_DIR" + cp "$HOME_DIR/config/genesis.json" "$SHARED_DIR/genesis.json" + touch "$FINAL_GENESIS_READY" + log "$SUCCESS" "Finalized genesis.json saved." +} + +# Main Setup Logic +log "$INFO" "Starting setup for ${NODE_NAME}..." + + +fetch_environment_variables +initialize_node +save_node_id +configure_rpc_and_p2p + +if [ "$NODE_NAME" == "regen-node1" ]; then + jq '.app_state.staking.params.bond_denom = "uregen"' "$HOME_DIR/config/genesis.json" > "$HOME_DIR/config/genesis_tmp.json" + mv "$HOME_DIR/config/genesis_tmp.json" "$HOME_DIR/config/genesis.json" + log "$SUCCESS" "Modified genesis.json for ${NODE_NAME}." + + add_validator_accounts_to_genesis + + # Save initial genesis.json and notify other nodes + cp "$HOME_DIR/config/genesis.json" "$SHARED_DIR/genesis.json" + touch "$INITIAL_GENESIS_READY" + log "$SUCCESS" "Initial genesis.json saved." + + wait_for_gentx_files + collect_and_finalize_genesis +else + wait_for_initial_genesis + generate_gentx + wait_for_final_genesis +fi + + +log "$INFO" "Starting ${NODE_NAME}..." +exec regen start --home "$HOME_DIR" --minimum-gas-prices="0.025uregen" diff --git a/scripts/devnet/setup.sh b/scripts/devnet/setup.sh new file mode 100755 index 0000000000..cf5f864880 --- /dev/null +++ b/scripts/devnet/setup.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +set -e + +# 🎨 Colors for better visibility +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +# 🚀 Start the setup +echo -e "${GREEN}🚀 Starting the Regen network setup...${NC}" + +# Check if node count is provided; default to 3 +NODE_COUNT=${1:-3} +echo -e "🔢 Setting up $NODE_COUNT nodes." + +# 🧹 Clean up existing directories +echo -e "🧹 Cleaning up existing directories..." +rm -rf ./shared ./node*_data .env docker-compose.yaml + +# Temporary directory for key generation +TEMP_DIR=$(mktemp -d) +echo -e "🔑 Generating validator keys in temporary directory: $TEMP_DIR" + +NODE_NAMES=() +NODE_ADDRESSES=() +NODE_MNEMONICS=() + +# 📦 Generate keys for each node +for i in $(seq 1 "$NODE_COUNT"); do + NODE="regen-node$i" + NODE_NAMES+=("$NODE") + echo -e "🔐 Generating keys for ${GREEN}$NODE${NC}..." + + NODE_HOME="$TEMP_DIR/$NODE" + mkdir -p "$NODE_HOME" + KEY_OUTPUT=$(regen keys add my_validator --keyring-backend test --home "$NODE_HOME" --output json) + + ADDRESS=$(echo "$KEY_OUTPUT" | jq -r '.address') + MNEMONIC=$(echo "$KEY_OUTPUT" | jq -r '.mnemonic') + + NODE_ADDRESSES+=("$ADDRESS") + NODE_MNEMONICS+=("$MNEMONIC") + + echo -e "📬 Address for ${GREEN}$NODE${NC}: ${ADDRESS}" +done + +# 📝 Write the .env file +echo -e "📝 Writing ${GREEN}.env${NC} file..." +rm -f .env +for i in "${!NODE_NAMES[@]}"; do + NODE="${NODE_NAMES[$i]}" + ADDRESS="${NODE_ADDRESSES[$i]}" + MNEMONIC="${NODE_MNEMONICS[$i]}" + NODE_ENV_NAME=$(echo "${NODE^^}" | tr '-' '_') + + echo "${NODE_ENV_NAME}_VALIDATOR_ADDRESS=${ADDRESS}" >> .env + echo "${NODE_ENV_NAME}_VALIDATOR_MNEMONIC=\"${MNEMONIC}\"" >> .env +done + +# 📝 Generate `docker-compose.yaml` +echo -e "📝 Generating ${GREEN}docker-compose.yaml${NC} file..." +cat < docker-compose.yaml +services: +EOF + +# ⚙️ Assign non-overlapping ports for each node +BASE_PORT=26000 # Start from a clean base to avoid collisions +for i in $(seq 0 $((NODE_COUNT - 1))); do + P2P_PORT=$((BASE_PORT + i * 3)) # P2P port + RPC_PORT=$((BASE_PORT + i * 3 + 1)) # RPC port + GRPC_PORT=$((BASE_PORT + i * 3 + 2)) # gRPC port + + + NODE="regen-node$((i + 1))" + + cat <> docker-compose.yaml + $NODE: + build: + context: . + dockerfile: Dockerfile + container_name: $NODE + environment: + - NODE_NAME=$NODE + - NODE_COUNT=$NODE_COUNT + - P2P_PORT=$P2P_PORT + - RPC_PORT=$RPC_PORT + - GRPC_PORT=$GRPC_PORT +$(for j in $(seq 1 "$NODE_COUNT"); do + PEER_NODE="regen-node$j" + PEER_ENV=$(echo "${PEER_NODE^^}" | tr '-' '_') + echo " - ${PEER_ENV}_VALIDATOR_ADDRESS=\${${PEER_ENV}_VALIDATOR_ADDRESS}" + echo " - ${PEER_ENV}_VALIDATOR_MNEMONIC=\${${PEER_ENV}_VALIDATOR_MNEMONIC}" +done) + volumes: + - ./shared/node:/mnt/nvme/shared + - ./shared/node$i-conf:/mnt/nvme/.regen + - ./entrypoint.sh:/entrypoint.sh + networks: + - regen-network + ports: + - "${RPC_PORT}:${RPC_PORT}" + - ":${P2P_PORT}:${P2P_PORT}" + - ":${P2P_PORT}:${P2P_PORT}" + entrypoint: ["/bin/bash", "/entrypoint.sh"] + +EOF +done + +cat <> docker-compose.yaml +networks: + regen-network: + driver: bridge +EOF + +echo -e "${GREEN}✅ docker-compose.yaml${NC} generated." + +# 🐳 Start the Docker containers +echo -e "${GREEN}🐳 Starting the Regen network with Docker Compose...${NC}" +docker compose up --build + +# 🧹 Clean up temporary files +rm -rf "$TEMP_DIR" +echo -e "🧹 Cleaned up temporary files." diff --git a/scripts/devnet/state_init.example.sh b/scripts/devnet/state_init.example.sh new file mode 100755 index 0000000000..2c0004b739 --- /dev/null +++ b/scripts/devnet/state_init.example.sh @@ -0,0 +1,83 @@ +#!/bin/bash +set -e + +# Constants +CHAIN_ID="regen-devnet" +HOME_DIR="./.regen" # Use the current directory for state initialization + +# Colors and Emojis +GREEN='\033[0;32m' +NC='\033[0m' +INFO="${GREEN}ℹ️${NC}" +SUCCESS="${GREEN}✅${NC}" +WAIT="${GREEN}⏳${NC}" + +# Helper function to log messages +log() { echo -e "${1} ${2}"; } + +# Load validator addresses and keys from .env +log "$INFO" "Loading environment variables..." +source .env + +# Ensure required addresses are available +VALIDATOR_1=${REGEN_NODE1_VALIDATOR_ADDRESS} +VALIDATOR_2=${REGEN_NODE2_VALIDATOR_ADDRESS} +MNEMONIC_1=${REGEN_NODE1_VALIDATOR_MNEMONIC} +MNEMONIC_2=${REGEN_NODE2_VALIDATOR_MNEMONIC} + +# Function to import validator accounts if they don't exist +import_key_if_not_exists() { + local name=$1 + local mnemonic=$2 + + if regen keys show "$name" --keyring-backend test --home "$HOME_DIR" &> /dev/null; then + log "$SUCCESS" "Key '$name' already exists in the keyring. Skipping import." + else + log "$INFO" "Importing key for '$name' into the keyring..." + echo "$mnemonic" | regen keys add "$name" --recover --keyring-backend test --home "$HOME_DIR" --output json + log "$SUCCESS" "Key '$name' imported successfully." + fi +} + +# Import validator accounts +log "$INFO" "Importing validator accounts into the keyring..." +import_key_if_not_exists "validator1" "$MNEMONIC_1" +import_key_if_not_exists "validator2" "$MNEMONIC_2" + +# Check if RPC is available +RPC_URL="http://$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' regen-node1):26657" + +log "$INFO" "Checking if RPC is available at $RPC_URL..." + +check_rpc_available() { + local retries=5 + while [ $retries -gt 0 ]; do + if curl -s "$RPC_URL" > /dev/null; then + log "$SUCCESS" "RPC endpoint is available." + return 0 + else + log "$WAIT" "Waiting for RPC to be available... ($retries retries left)" + sleep 5 + retries=$((retries - 1)) + fi + done + + log "$WAIT" "RPC is still unavailable after multiple attempts." + exit 1 +} + +check_rpc_available + +# 1. Submit a governance proposal +log "$INFO" "Submitting a governance proposal..." +regen tx gov submit-proposal text "Upgrade Proposal" \ + --description "Proposal to upgrade the testnet" \ + --deposit 10000000uregen --from validator1 \ + --chain-id "$CHAIN_ID" --home "$HOME_DIR" --keyring-backend test --yes + +# 2. Vote on the proposal +log "$INFO" "Validator 2 voting YES on the proposal..." +regen tx gov vote 1 yes --from validator2 \ + --chain-id "$CHAIN_ID" --home "$HOME_DIR" --keyring-backend test --yes + +log "$SUCCESS" "State initialization complete."