diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..7d42caa --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,74 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - "*" + pull_request: + types: [opened, synchronize] + workflow_dispatch: + inputs: + branch: + description: "Branch to run tests on" + required: true + default: "main" + release: + types: [created] + +jobs: + build: + runs-on: self-hosted + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + + - name: Build binary + run: make build + + - name: Upload binary artifact + if: github.event_name == 'release' + uses: actions/upload-artifact@v2 + with: + name: galacticad + path: build/galacticad + + test: + runs-on: self-hosted + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Run tests + run: make test + + - name: Test status check + if: failure() + run: exit 1 + + permissions: + pull-requests: write + + manual-tests: + runs-on: self-hosted + needs: test + if: github.event_name == 'workflow_dispatch' + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: "1.17" + + - name: Run manual tests + run: go test ./... diff --git a/Makefile b/Makefile index f661120..4b86891 100644 --- a/Makefile +++ b/Makefile @@ -144,3 +144,11 @@ install: build mkdir -p $(BINDIR) cp -f $(BUILDDIR)/$(GALACTICA_BINARY) $(BINDIR)/$(GALACTICA_BINARY) @echo "Galactica has been installed to $(BINDIR)" + +help: ## Show this help + @printf "\033[33m%s:\033[0m\n" 'Available commands' + @awk 'BEGIN {FS = ":.*?## "} /^[[:alpha:][:punct:]]+:.*?## / {printf " \033[32m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +ifeq ($(CI),true) +include tests/Makefile +endif diff --git a/localnet/init-configs-localnet.sh b/localnet/init-configs-localnet.sh index d8e3242..d6b17ef 100755 --- a/localnet/init-configs-localnet.sh +++ b/localnet/init-configs-localnet.sh @@ -21,13 +21,12 @@ CHAIN_ID=${2:-"galactica_9302-1"} KEYRING_BACKEND=${3:-"test"} BASE_DENOM=${4:-"agnet"} DISPLAY_DENOM=${5:-"gnet"} -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) # localKey address 0x7cb61d4117ae31a12e393a1cfa3bac666481d02e PREDEFINED_KEY_NAME="localkey" PREDEFINED_KEY_MNEMONIC="gesture inject test cycle original hollow east ridge hen combine junk child bacon zero hope comfort vacuum milk pitch cage oppose unhappy lunar seat" - function gala() { ./build/galacticad --home "$MAIN_PATH_HOME" "$@" } @@ -50,16 +49,12 @@ function add_key() { } function add_key_predefined() { - echo $PREDEFINED_KEY_MNEMONIC | gala keys add \ - $PREDEFINED_KEY_NAME \ - --recover \ - --keyring-backend $KEYRING_BACKEND \ - --algo "eth_secp256k1" \ - --keyring-dir $MAIN_PATH_HOME/ -} - -function init_localtestnet() { - gala init localtestnet --chain-id $CHAIN_ID --default-denom $BASE_DENOM + echo $PREDEFINED_KEY_MNEMONIC | gala keys add \ + $PREDEFINED_KEY_NAME \ + --recover \ + --keyring-backend $KEYRING_BACKEND \ + --algo "eth_secp256k1" \ + --keyring-dir $MAIN_PATH_HOME/ } function configure_app() { @@ -69,41 +64,40 @@ function configure_app() { # sed -i.backup 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/' $MAIN_PATH_CONFIG/app.toml sed -i.backup 's/address = "localhost:9090"/address = "0.0.0.0:9090"/' $MAIN_PATH_CONFIG/app.toml sed -i.backup '/\[grpc-web\]/,+7 s/address = "localhost:9091"/address = "0.0.0.0:9091"/' $MAIN_PATH_CONFIG/app.toml - sed -i.backup 's/pruning = "default"/pruning = "nothing"/g' $MAIN_PATH_CONFIG/app.toml - sed -i.backup 's/minimum-gas-prices = "0stake"/minimum-gas-prices = "10'$BASE_DENOM'"/g' $MAIN_PATH_CONFIG/app.toml + sed -i.backup 's/pruning = "default"/pruning = "nothing"/g' $MAIN_PATH_CONFIG/app.toml + sed -i.backup 's/minimum-gas-prices = "0stake"/minimum-gas-prices = "10'$BASE_DENOM'"/g' $MAIN_PATH_CONFIG/app.toml sed -i.backup '/\[telemetry\]/,+8 s/enabled = false/enabled = true/' $MAIN_PATH_CONFIG/app.toml sed -i.backup '/\[telemetry\]/,+20 s/prometheus-retention-time = 0/prometheus-retention-time = 60/' $MAIN_PATH_CONFIG/app.toml sed -i.backup '/global-labels = \[/a\ \["chain_id", "'$CHAIN_ID'"\], ' $MAIN_PATH_CONFIG/app.toml - sed -i.backup 's/timeout_propose = ".*"/timeout_propose = "3s"/g' $MAIN_PATH_CONFIG/app.toml - sed -i.backup 's/timeout_propose_delta = ".*"/timeout_propose_delta = "500ms"/g' $MAIN_PATH_CONFIG/app.toml - sed -i.backup 's/timeout_prevote = ".*"/timeout_prevote = "1s"/g' $MAIN_PATH_CONFIG/app.toml - sed -i.backup 's/timeout_prevote_delta = ".*"/timeout_prevote_delta = "500ms"/g' $MAIN_PATH_CONFIG/app.toml - sed -i.backup 's/timeout_precommit = ".*"/timeout_precommit = "1s"/g' $MAIN_PATH_CONFIG/app.toml - sed -i.backup 's/timeout_precommit_delta = ".*"/timeout_precommit_delta = "500ms"/g' $MAIN_PATH_CONFIG/app.toml - sed -i.backup 's/timeout_commit = ".*"/timeout_commit = "5s"/g' $MAIN_PATH_CONFIG/app.toml - + sed -i.backup 's/timeout_propose = ".*"/timeout_propose = "3s"/g' $MAIN_PATH_CONFIG/app.toml + sed -i.backup 's/timeout_propose_delta = ".*"/timeout_propose_delta = "500ms"/g' $MAIN_PATH_CONFIG/app.toml + sed -i.backup 's/timeout_prevote = ".*"/timeout_prevote = "1s"/g' $MAIN_PATH_CONFIG/app.toml + sed -i.backup 's/timeout_prevote_delta = ".*"/timeout_prevote_delta = "500ms"/g' $MAIN_PATH_CONFIG/app.toml + sed -i.backup 's/timeout_precommit = ".*"/timeout_precommit = "1s"/g' $MAIN_PATH_CONFIG/app.toml + sed -i.backup 's/timeout_precommit_delta = ".*"/timeout_precommit_delta = "500ms"/g' $MAIN_PATH_CONFIG/app.toml + sed -i.backup 's/timeout_commit = ".*"/timeout_commit = "5s"/g' $MAIN_PATH_CONFIG/app.toml # configure config settings - sed -i.backup 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/g' $MAIN_PATH_CONFIG/config.toml - sed -i.backup 's/proxy_app = "tcp:\/\/127.0.0.1:26658"/proxy_app = "tcp:\/\/127.0.0.1:26658"/g' $MAIN_PATH_CONFIG/config.toml - sed -i.backup 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/g' $MAIN_PATH_CONFIG/config.toml - sed -i.backup 's/max_num_inbound_peers = 40/max_num_inbound_peers = 120/g' $MAIN_PATH_CONFIG/config.toml - sed -i.backup 's/max_num_outbound_peers = 10/max_num_outbound_peers = 60/g' $MAIN_PATH_CONFIG/config.toml + sed -i.backup 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/g' $MAIN_PATH_CONFIG/config.toml + sed -i.backup 's/proxy_app = "tcp:\/\/127.0.0.1:26658"/proxy_app = "tcp:\/\/127.0.0.1:26658"/g' $MAIN_PATH_CONFIG/config.toml + sed -i.backup 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/g' $MAIN_PATH_CONFIG/config.toml + sed -i.backup 's/max_num_inbound_peers = 40/max_num_inbound_peers = 120/g' $MAIN_PATH_CONFIG/config.toml + sed -i.backup 's/max_num_outbound_peers = 10/max_num_outbound_peers = 60/g' $MAIN_PATH_CONFIG/config.toml } function update_genesis_json() { local jq_command=$1 local file_path=${2:-"$MAIN_PATH_CONFIG/genesis.json"} - jq "$jq_command" "$file_path" > "${file_path}.tmp" && mv "${file_path}.tmp" "$file_path" + jq "$jq_command" "$file_path" >"${file_path}.tmp" && mv "${file_path}.tmp" "$file_path" } function configure_genesis() { - local staking_min_deposit="$1" - local total_supply="$2" + local staking_min_deposit="$1" # 5000000000000000000000 + local total_supply="$2" # 1000000000000000000000000 local faucet_address="$3" local voting_period="60s" local unbonding_time="30s" @@ -144,7 +138,7 @@ function configure_genesis() { "uri_hash": "" }' update_genesis_json '.app_state.bank.send_enabled[0] = {"denom": "'$BASE_DENOM'", "enabled": true}' -# update_genesis_json '.app_state.bank.supply[0] = {"denom": "'$BASE_DENOM'", "amount": "'$total_supply'"}' + # update_genesis_json '.app_state.bank.supply[0] = {"denom": "'$BASE_DENOM'", "amount": "'$total_supply'"}' # EVM params update_genesis_json '.app_state.evm.params.evm_denom = "'$BASE_DENOM'"' @@ -169,8 +163,8 @@ function add_genesis_account() { local amount="$2" gala add-genesis-account \ - "$(gala keys show $account_name -a --keyring-dir $MAIN_PATH_HOME)" \ - $amount + "$(gala keys show $account_name -a --keyring-dir $MAIN_PATH_HOME)" \ + $amount } function initialize_validator() { @@ -181,36 +175,26 @@ function initialize_validator() { local validator_home=$MAIN_PATH_HOME/validators/$moniker gala init \ - $moniker \ - --chain-id $CHAIN_ID \ - --default-denom $BASE_DENOM \ - --home $validator_home + $moniker \ + --chain-id $CHAIN_ID \ + --default-denom $BASE_DENOM \ + --home $validator_home cp $MAIN_PATH_CONFIG/genesis.json $validator_home/config/genesis.json gala gentx \ - $moniker \ - $staking_amount \ - --ip $ip \ - --p2p-port $p2p_port \ - --home $validator_home \ - --keyring-dir $MAIN_PATH_HOME \ - --keyring-backend $KEYRING_BACKEND + $moniker \ + $staking_amount \ + --ip $ip \ + --p2p-port $p2p_port \ + --home $validator_home \ + --keyring-dir $MAIN_PATH_HOME \ + --keyring-backend $KEYRING_BACKEND mkdir -p $MAIN_PATH_CONFIG/gentx/ cp $validator_home/config/gentx/* $MAIN_PATH_CONFIG/gentx/ } -function collect_gentxs() { - gala collect-gentxs - rm $MAIN_PATH_CONFIG/node_key.json - rm $MAIN_PATH_CONFIG/priv_validator_key.json -} - -function validate_genesis() { - gala validate-genesis -} - function configure_validator() { local moniker=$1 local ip_address=$2 @@ -225,18 +209,21 @@ function configure_validator() { # Filter out the IP address of the current validator from persistent_peers local persistent_peers=$(cat $validator_home/config/config.toml | grep persistent_peers | cut -d '=' -f 2 | tr -d '"') - IFS=',' read -ra parts <<< "$persistent_peers" + IFS=',' read -ra parts <<<"$persistent_peers" filtered_parts=() for part in "${parts[@]}"; do if [[ ! "$part" =~ $ip_address ]]; then filtered_parts+=("$part") fi done - local new_persistent_peers=$(IFS=','; echo "${filtered_parts[*]}") + local new_persistent_peers=$( + IFS=',' + echo "${filtered_parts[*]}" + ) echo "Validator $moniker persistent_peers: $new_persistent_peers" sed -i.backup "s/\(persistent_peers *= *\"\).*\(\" *\)/\1$new_persistent_peers\2/" $validator_home/config/config.toml - sed -i.backup 's/moniker = "localtestnet"/moniker = "'$moniker'"/g' $validator_home/config/config.toml + sed -i.backup 's/moniker = "localtestnet"/moniker = "'$moniker'"/g' $validator_home/config/config.toml local key=$(gala keys unsafe-export-eth-key --keyring-backend test --keyring-dir ./$MAIN_PATH_HOME $moniker) yes '00000000' | gala keys unsafe-import-eth-key --keyring-backend test --keyring-dir ./$MAIN_PATH_HOME/validators/$moniker $moniker $key --chain-id $CHAIN_ID @@ -246,7 +233,7 @@ function configure_faucet() { local key=$(gala keys unsafe-export-eth-key --keyring-backend test --keyring-dir ./$MAIN_PATH_HOME faucet) yes '00000000' | gala keys unsafe-import-eth-key --keyring-backend test --keyring-dir ./$MAIN_PATH_HOME/faucet faucet $key --chain-id $CHAIN_ID - echo $key > ./$MAIN_PATH_HOME/faucet/PRIVATE_KEY + echo $key >./$MAIN_PATH_HOME/faucet/PRIVATE_KEY } function main() { @@ -262,12 +249,11 @@ function main() { add_key "reticulum01" add_key "reticulum02" add_key "reticulum03" - add_key "vlval" add_key "treasury" add_key "faucet" add_key_predefined - init_localtestnet + gala init localtestnet --chain-id $CHAIN_ID --default-denom $BASE_DENOM configure_app local faucet_address_bech32=$(gala keys show faucet -a --keyring-dir $MAIN_PATH_HOME) @@ -280,23 +266,27 @@ function main() { # Add genesis accounts add_genesis_account "reticulum01" "10000000000000000000000$BASE_DENOM" add_genesis_account "reticulum02" "10000000000000000000000$BASE_DENOM" + add_genesis_account "reticulum03" "10000000000000000000000$BASE_DENOM" + add_genesis_account "faucet" "10000000000000000000000$BASE_DENOM" add_genesis_account "localkey" "10000000000000000000000$BASE_DENOM" add_genesis_account "treasury" "950000000000000000000000$BASE_DENOM" - add_genesis_account "vlval" "10000000000000000000000$BASE_DENOM" - # Initialize validators - initialize_validator "reticulum01" "127.0.1.1" 26656 "9000000000000000000000$BASE_DENOM" - initialize_validator "reticulum02" "127.0.1.2" 26656 "9000000000000000000000$BASE_DENOM" - initialize_validator "vlval" "95.154.64.137" 26656 "9000000000000000000000$BASE_DENOM" + initialize_validator "reticulum01" "127.0.0.2" 26656 "9000000000000000000000$BASE_DENOM" + initialize_validator "reticulum02" "127.0.0.3" 26656 "9000000000000000000000$BASE_DENOM" + initialize_validator "reticulum03" "127.0.0.4" 26656 "9000000000000000000000$BASE_DENOM" - collect_gentxs - validate_genesis + gala collect-gentxs + + rm $MAIN_PATH_CONFIG/node_key.json + rm $MAIN_PATH_CONFIG/priv_validator_key.json + + gala validate-genesis - configure_validator "reticulum01" "127.0.1.1" - configure_validator "reticulum02" "127.0.1.2" - configure_validator "vlval" "95.154.64.137" + configure_validator "reticulum01" "127.0.0.2" + configure_validator "reticulum02" "127.0.0.3" + configure_validator "reticulum03" "127.0.0.4" configure_faucet } diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..3f6dd9c --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,2 @@ +test: ## some tests + cd tests/galacli && $(MAKE) diff --git a/tests/galacli/.gitignore b/tests/galacli/.gitignore index c8dc11c..c4f4f28 100644 --- a/tests/galacli/.gitignore +++ b/tests/galacli/.gitignore @@ -2,3 +2,4 @@ data config node0* +__pycache__ diff --git a/tests/galacli/Makefile b/tests/galacli/Makefile new file mode 100644 index 0000000..c52ed11 --- /dev/null +++ b/tests/galacli/Makefile @@ -0,0 +1,20 @@ +uname=$(shell uname -s) +is_darwin :=$(filter Darwin,$(uname)) + +all: $(if $(is_darwin),network,) clean start + +clean: + rm -rf node0* + +.venv/bin/python: + uv venv + uv sync + +start: .venv/bin/python + .venv/bin/python galacli.py + +ifeq ($(uname),Darwin) +network: /tmp/127.0.0.2 /tmp/127.0.0.3 /tmp/127.0.0.4 +/tmp/127.0.0.%: + sudo ifconfig lo0 alias $(shell basename $@) up && touch $@ +endif diff --git a/tests/galacli/galacli.py b/tests/galacli/galacli.py index 95829a1..8f21921 100644 --- a/tests/galacli/galacli.py +++ b/tests/galacli/galacli.py @@ -1,9 +1,14 @@ from collections import defaultdict +import os +import shutil import subprocess +from typing import Dict, List from dateutil.parser import isoparse import json import time import socket +import re + # import os import sys @@ -24,8 +29,96 @@ DEFAULT_TEST_MONIKER = "test-node01" DEFAULT_TEST_CHAINID = "test_41239-41239" -PREDEFINED_KEY_MNEMONIC = "gesture inject test cycle original hollow east ridge hen combine junk child bacon zero hope comfort vacuum milk pitch cage oppose unhappy lunar seat" +PREDEFINED_KEY_MNEMONIC_TREASURY = "gesture inject test cycle original hollow east ridge hen combine junk child bacon zero hope comfort vacuum milk pitch cage oppose unhappy lunar seat" PREDEFINED_KEY_MNEMONIC_FAUCET = "heart grape ignore face equip monkey keep armor tumble donkey final horror harsh way retire this enforce pave there unfair scrap shine physical since" +PREDEFINED_KEY_MNEMONIC_NODE_KEYS = [ + "kick treat protect present permit business own nuclear ranch ancient around deposit dignity cabin kiwi parade sister market must crime tag update yellow theory", + "minimum sing arrow way comfort obvious purse piece reward simple fitness fence october dutch genius spike sunset empower limit dog dutch kid online file", + "uniform spread february wife quality device mix fish rapid win improve van eagle target icon home charge birth reward slogan season robust thunder over", +] + + +class GnetAmount: + DENOMINATIONS = {"agnet": 1, "ugnet": 1e12, "mgnet": 1e15, "gnet": 1e18} + + def __init__(self, amount): + if isinstance(amount, float): + self.amount = amount + else: + match = re.match(r"([\d\.]+)(agnet|ugnet|mgnet|gnet)", amount) + if match: + self.amount = float(match.group(1)) * self.DENOMINATIONS[match.group(2)] + else: + raise ValueError(f"Invalid amount format: {amount}") + + def __repr__(self): + for denomination, value in sorted( + self.DENOMINATIONS.items(), key=lambda item: -item[1] + ): + if self.amount % value == 0: + return f"{self.__class__.__name__}({int(self.amount // value)}{denomination})" + + def __str__(self): + for denomination, value in sorted( + self.DENOMINATIONS.items(), key=lambda item: item[1] + ): + if self.amount % value == 0: + return f"{str(int(self.amount // value))}{denomination}" + + def __format__(self, format_spec): + return str(self) + + def __add__(self, other): + if not isinstance(other, GnetAmount): + other = GnetAmount(other) + return GnetAmount(self.amount + other.amount) + # else: + # raise TypeError( + # f"unsupported operand type(s) for +: '{self.__class__.__name__}' and '{type(other).__name__}'" + # ) + + def __sub__(self, other): + if isinstance(other, GnetAmount): + return GnetAmount(self.amount - other.amount) + else: + raise TypeError( + f"unsupported operand type(s) for -: '{self.__class__.__name__}' and '{type(other).__name__}'" + ) + + def __eq__(self, other): + if isinstance(other, GnetAmount): + return self.amount == other.amount + else: + return False + + def __lt__(self, other): + if isinstance(other, GnetAmount): + return self.amount < other.amount + else: + raise TypeError( + f"unsupported operand type(s) for <: '{self.__class__.__name__}' and '{type(other).__name__}'" + ) + + def __mul__(self, other): + if isinstance(other, (int, float)): + return GnetAmount(self.amount * other) + else: + raise TypeError( + f"unsupported operand type(s) for *: '{self.__class__.__name__}' and '{type(other).__name__}'" + ) + + def __truediv__(self, other): + if isinstance(other, (int, float)): + return GnetAmount(self.amount / other) + elif isinstance(other, (GnetAmount)): + return float(self.amount / other.amount) + else: + raise TypeError( + f"unsupported operand type(s) for /: '{self.__class__.__name__}' and '{type(other).__name__}'" + ) + + def min_denom_amount_str(self) -> str: + return str(int(self.amount)) def wait_for_port(port, host="127.0.0.1", timeout=40.0): @@ -76,7 +169,7 @@ def interact(cmd, ignore_error=False, input=None, **kwargs): **kwargs, ) # begin = time.perf_counter() - (stdout, _) = proc.communicate(input=input) + stdout, _ = proc.communicate(input=input) # print('[%.02f] %s' % (time.perf_counter() - begin, cmd)) if not ignore_error: assert proc.returncode == 0, f'{stdout.decode("utf-8")} ({cmd})' @@ -260,6 +353,11 @@ def save(self): with open(self.path, "w") as file: json.dump(self.config, file) + def save_to(self, path): + "save config to self.path file in toml format" + with open(path, "w") as file: + json.dump(self.config, file) + def deep_update(self, original, new): "Recursive update of nested dictionaries" for key, value in new.items(): @@ -301,6 +399,12 @@ def __init__( self.output_format = output_format self.broadcast_mode = broadcast_mode self.error = None + self.config = None + self.app_config = None + self.client_config = None + self.load_config() + + def load_config(self): if Path(self.data_dir / "config/config.toml").exists(): self.config = GalaToml(self.data_dir / "config/config.toml") if Path(self.data_dir / "config/app.toml").exists(): @@ -397,6 +501,7 @@ def create_account(self, name, mnemonic=None): "add", name, home=self.data_dir, + algo="eth_secp256k1", output="json", keyring_backend=self.keyring_backend, ) @@ -428,17 +533,17 @@ def delete_account(self, name): ) ############################## - # Tendermint + # Tendermint => Cometbft ############################## - def consensus_address(self): - "get tendermint consensus address" - output = self.raw("tendermint", "show-address", home=self.data_dir) + def consensus_address(self) -> str: + "get comet consensus address" + output = self.raw("comet", "show-address", home=self.data_dir) return output.decode().strip() - def node_id(self): - "get tendermint node id" - output = self.raw("tendermint", "show-node-id", home=self.data_dir) + def node_id(self) -> str: + "get comet node id" + output = self.raw("comet", "show-node-id", home=self.data_dir) return output.decode().strip() def export(self): @@ -503,6 +608,7 @@ def __init__( # node_id=None, moniker=DEFAULT_TEST_MONIKER, keyring_backend="test", + node_addr="127.0.0.1", ): super().__init__( cmd=cmd, @@ -513,17 +619,58 @@ def __init__( ) # self.node_id = node_id self.moniker = moniker + self.node_addr = node_addr + self.account = None + self.node_rpc = f"tcp://{self.node_addr}:26657" self.process = None - def start(self): ... + def initial_configure_node(self): + self.raw("config", "set", "client", "keyring-backend", "test") + self.load_config() + self.client_config.edit({"output": "json", "chain-id": self.chain_id}) + self.config.edit({"moniker": self.moniker}) + ## set self network addr + self.config.apply_addr(self.node_addr) + self.client_config.apply_addr(self.node_addr) + self.app_config.apply_addr(self.node_addr) + ## other initial config + self.app_config.edit({"api": {"enable": True}}) + self.app_config.edit({"pruning": "nothing"}) + self.app_config.edit({"minimum-gas-prices": f"10{DEFAULT_DENOM}"}) + self.app_config.edit( + { + "telemetry": { + "service-name": "galacticad", + "enabled": True, + "prometheus-retention-time": "60", + "global-labels": [["chain-id", self.chain_id]], + } + } + ) + self.config.edit( + {"moniker": self.moniker, "log_format": "json", "log_level": "debug"} + ) + self.config.edit({"consensus": {"timeout_commit": "1s"}}) + self.config.edit( + { + "rpc": { + "cors_allowed_origins": [ + "*", + ] + } + } + ) + + async def start(self): + await self.run("start", home=self.data_dir, chain_id=self.chain_id) def node_info(self): return requests.get( f"{self.node_rpc_http}/cosmos/staking/v1beta1/validators/{self.node_id}" ).json() - def init(self, moniker=None): - "generate initial config with genesis.json" + def init_node(self, moniker=None): + "### Generate initial config with genesis.json" moniker = moniker or self.moniker or DEFAULT_TEST_MONIKER return self.raw( "init", @@ -568,145 +715,157 @@ async def terminate(self, timeout=30): await self.process.wait() return self.process.returncode + def set_address_in_configs(self, addr: str): + for c in (self.client_config, self.app_config, self.config): + if c: + c.apply_addr(addr) -async def main(): - chain_id = DEFAULT_TEST_CHAINID - moniker = "test-node01" - g_client = GalaClientConfig("/dev/null") - g_client.config.update( - dict( - chain_id="test_41239-41239", - keyring_backend="test", - output="json", - node="tcp://127.0.0.2:26657", - broadcast_mode="sync", - ) - ) - gn1 = GalaNodeCLI( - data_dir="node01", - chain_id=chain_id, - moniker=moniker, - node_rpc="tcp://127.0.0.2:26657", - ) - gn1.init() - - gn1.client_config = GalaClientConfig(gn1.data_dir / "./config/client.toml") - gn1.client_config.edit( - { - "chain-id": chain_id, - "keyring-backend": "test", - "output": "json", - } - ) +class GalaNetwork: + "### Bunch of GalaNodes with some similar parameters" - gn1.app_config = GalaToml(gn1.data_dir / "./config/app.toml") - gn1.app_config.apply_addr("127.0.0.2") - gn1.app_config.edit({"api": {"enable": True}}) - gn1.app_config.edit({"pruning": "nothing"}) - gn1.app_config.edit({"minimum-gas-prices": f"10{DEFAULT_DENOM}"}) - gn1.app_config.edit( - { - "telemetry": { - "service-name": "galacticad", - "enabled": True, - "prometheus-retention-time": "60", - "global-labels": [["chain-id", DEFAULT_TEST_CHAINID]], - } - } - ) + def __init__(self, n_nodes=3, chain_id=DEFAULT_TEST_CHAINID, *args, **kwargs): + self.chain_id = chain_id + self.nodes: List[GalaNodeCLI] = [] + self.command_node = GalaNodeCLI( + data_dir="node00", moniker="node00", chain_id=chain_id + ) + for n in range(n_nodes): + name = f"node0{n + 1}" + self.nodes.append( + GalaNodeCLI( + moniker=name, + data_dir=name, + node_addr=f"127.0.0.{ 2 + n }", ## 127.0.0.2 127.0.0.3 ... + chain_id=self.chain_id, + *args, + **kwargs, + ) + ) - gn1.config = GalaToml(gn1.data_dir / "./config/config.toml") - gn1.config.edit({"moniker": "test-node01", "log_format": "json"}) - gn1.config.apply_addr("127.0.0.2") - gn1.config.edit({"consensus": {"timeout_commit": "1s"}}) - gn1.config.edit( - { - "rpc": { - "cors_allowed_origins": [ - "*", - ] + async def initial_configure_network(self): + for n in self.nodes: + await n.initial_configure_node() + + async def start(self): + "### Start every node in network" + for node in self.nodes: + await node.start() + + async def stop(self): + "### Stop every node in network" + for node in self.nodes: + await node.terminate() + + async def check_live(self): + "### Check that node is running" + ... + + def configure_genesis(self): + # total_supply = str(GnetAmount("200gnet")) ## will be calculated later + staking_min_deposit = GnetAmount("100gnet").min_denom_amount_str() + max_deposit_period = "600s" + unbonding_time = "30s" + ## faucet initialzed here because its address neede in genesis minting config + self.faucet = self.command_node.create_account( + "faucet", PREDEFINED_KEY_MNEMONIC_FAUCET + ) + faucet_address = self.faucet["address"] + inflation_validators_share = "0.99933" + inflation_faucet_share = "0.00067" + + block_max_gas = "40000000" + block_max_bytes = "22020096" + time_iota_ms = "1000" + voting_period = "60s" + expedited_voting_period = "30s" + genesis = Genesis(path=self.command_node.data_dir / "config/genesis.json") + genesis.edit( + { + "consensus": { + "params": { + "block": { + "max_bytes": block_max_bytes, + "max_gas": block_max_gas, + "time_iota_ms": time_iota_ms, + } + } + } } - } - ) - # d = andr_app_config.diff(app_config) - print("config created") - print("configure genesis") - - total_supply = str(int(200e18)) - staking_min_deposit = str(int(100e18)) - max_deposit_period = "600s" - unbonding_time = "30s" - - faucet = gn1.create_account("faucet", PREDEFINED_KEY_MNEMONIC_FAUCET) - faucet_address = faucet["address"] - inflation_validators_share = "0.99933" - inflation_faucet_share = "0.00067" - - block_max_gas = "40000000" - block_max_bytes = "22020096" - time_iota_ms = "1000" - voting_period = "60s" - expedited_voting_period = "30s" - genesis = Genesis(path=gn1.data_dir / "config/genesis.json") - genesis.edit({"consensus": {"params": {"block": {"max_bytes": block_max_bytes}}}}) - genesis.edit({"consensus": {"params": {"block": {"max_gas": block_max_gas}}}}) - genesis.edit({"consensus": {"params": {"block": {"time_iota_ms": time_iota_ms}}}}) - genesis.edit( - {"app_state": {"gov": {"voting_params": {"voting_period": voting_period}}}} - ) + ) + genesis.edit( + {"app_state": {"gov": {"voting_params": {"voting_period": voting_period}}}} + ) - update_genesis = { - "app_state": { - "gov": { - "deposit_params": { - "min_deposit": [ - {"denom": BASE_DENOM, "amount": staking_min_deposit} - ] + update_genesis = { + "app_state": { + "gov": { + "deposit_params": { + "min_deposit": [ + {"denom": BASE_DENOM, "amount": staking_min_deposit} + ] + }, + "params": { + "min_deposit": [ + {"denom": BASE_DENOM, "amount": staking_min_deposit} + ], + "max_deposit_period": max_deposit_period, + "voting_period": voting_period, + "expedited_voting_period": expedited_voting_period, + }, + }, + "staking": { + "params": { + "bond_denom": BASE_DENOM, + "unbonding_time": unbonding_time, + } }, - "params": { - "min_deposit": [ - {"denom": BASE_DENOM, "amount": staking_min_deposit} + "crisis": {"constant_fee": {"denom": BASE_DENOM}}, + "mint": {"params": {"mint_denom": BASE_DENOM}}, + "bank": { + "denom_metadata": [ + { + "description": "The native staking token of the Galactica Network.", + "denom_units": [ + { + "denom": BASE_DENOM, + "exponent": 0, + "aliases": ["attognet"], + }, + { + "denom": "ugnet", + "exponent": 6, + "aliases": ["micrognet"], + }, + {"denom": DISPLAY_DENOM, "exponent": 18}, + ], + "base": BASE_DENOM, + "display": DISPLAY_DENOM, + "name": "Galactica Network", + "symbol": DISPLAY_DENOM.upper(), + "uri": "", + "uri_hash": "", + } ], - "max_deposit_period": max_deposit_period, - "voting_period": voting_period, - "expedited_voting_period": expedited_voting_period, + "send_enabled": [{"denom": BASE_DENOM, "enabled": True}], + # "supply": [{"denom": BASE_DENOM, "amount": total_supply}], }, - }, - "staking": { - "params": {"bond_denom": BASE_DENOM, "unbonding_time": unbonding_time} - }, - "crisis": {"constant_fee": {"denom": BASE_DENOM}}, - "mint": {"params": {"mint_denom": BASE_DENOM}}, - "bank": { - "denom_metadata": [ - { - "description": "The native staking token of the Galactica Network.", - "denom_units": [ - { - "denom": BASE_DENOM, - "exponent": 0, - "aliases": ["attognet"], - }, - {"denom": "ugnet", "exponent": 6, "aliases": ["micrognet"]}, - {"denom": DISPLAY_DENOM, "exponent": 18}, - ], - "base": BASE_DENOM, - "display": DISPLAY_DENOM, - "name": "Galactica Network", - "symbol": DISPLAY_DENOM.upper(), - "uri": "", - "uri_hash": "", - } - ], - "send_enabled": [{"denom": BASE_DENOM, "enabled": True}], - "supply": [{"denom": BASE_DENOM, "amount": total_supply}], - }, - "evm": {"params": {"evm_denom": BASE_DENOM}}, - "inflation": { - "params": { - "enable_inflation": True, - "mint_denom": BASE_DENOM, + "evm": {"params": {"evm_denom": BASE_DENOM}}, + "inflation": { + "params": { + "enable_inflation": True, + "mint_denom": BASE_DENOM, + "inflation_distribution": { + "validators_share": inflation_validators_share, + "other_shares": [ + { + "address": faucet_address, + "name": "faucet", + "share": inflation_faucet_share, + } + ], + }, + }, "inflation_distribution": { "validators_share": inflation_validators_share, "other_shares": [ @@ -718,44 +877,160 @@ async def main(): ], }, }, - "inflation_distribution": { - "validators_share": inflation_validators_share, - "other_shares": [ - { - "address": faucet_address, - "name": "faucet", - "share": inflation_faucet_share, - } - ], - }, }, - }, - } - genesis.edit(update_genesis) - print("add some genesis accounts") - test_node01_acc = gn1.create_account("test-node01") - # total_supply = 910e18 - gn1.add_genesis_account(test_node01_acc["address"], str(int(200e18)) + BASE_DENOM) - gn1.gentx( - "test-node01", - str(int(150e18)) + BASE_DENOM, - ip="127.0.0.2", - commission_rate="0.02", - details="test.node01.details", - ) - gn1.collect_gentxs(gn1.data_dir / "config/gentx") - gn1.validate_genesis() - print("start node...") + } + genesis.edit(update_genesis) + + def combine_seeds(self) -> Dict: + """ + ### Получение строк которые лягут в seeds или в persistent_peers + """ + not_combined = { + node.moniker: node.node_id() + "@" + node.node_addr + ":26656" + for node in self.nodes + } + combined = { + node.moniker: ",".join( + [not_combined[n] for n in not_combined if n != node.moniker] + ) + for node in self.nodes + } + return combined + + def configure_network(self): + """ + ### func for configuring gala network + + - [X] Init first node to get blank genesis.json + - [X] Edit config files via first node to set common config + - [X] Configure genesis to needed state + - [X] edit genesis.json + - [X] add some gentx + - [X] collect gentx + - [X] validate genesis + - [ ] Get tendermint node-id of each node + - [X] Put node folder + - [X] configure node + - [X] put key into node + - [X] put genesis.json to node config + - [ ] get tendermint node-id + - [ ] Edit individual configs to set some parameters throgh network + - [ ] Persistent peers + """ + + self.command_node.init_node( + moniker=self.command_node.moniker, + ) + self.configure_genesis() - await asyncio.sleep(1) - await gn1.run("start", home=gn1.data_dir, chain_id=gn1.chain_id) - await asyncio.sleep(1) - print(gn1.is_running()) - gn1.wait_for_block(5) - # await gn1.terminate() - # await asyncio.sleep(10) - if await gn1.terminate() == 0: - print("Success") + ## Create treasury account + treasury_acc = self.command_node.create_account( + "treasury", mnemonic=PREDEFINED_KEY_MNEMONIC_TREASURY + ) + if not self.faucet: + faucet_acc = self.command_node.create_account( + "faucet", mnemonic=PREDEFINED_KEY_MNEMONIC_FAUCET + ) + else: + faucet_acc = self.faucet + total_supply = GnetAmount("0gnet") + self.command_node.add_genesis_account( + treasury_acc["address"], GnetAmount("800gnet") + ) + self.command_node.add_genesis_account( + faucet_acc["address"], GnetAmount("100gnet") + ) + + total_supply += GnetAmount("800gnet") + GnetAmount("100gnet") + self.genesis = Genesis(self.command_node.data_dir / "config/genesis.json") + + ## Create accounts for nodes + for num, node in enumerate(self.nodes): + node.init_node() + self.genesis.save_to(node.data_dir / "config/genesis.json") + + node_genesis_supply = GnetAmount("200gnet") + node.account = node.create_account( + node.moniker, mnemonic=PREDEFINED_KEY_MNEMONIC_NODE_KEYS[num] + ) + node.add_genesis_account(node.account["address"], node_genesis_supply) + self.command_node.add_genesis_account( + node.account["address"], node_genesis_supply + ) + node.gentx( + node.moniker, + node_genesis_supply, + ip=node.node_addr, + commission_rate="0.02", + details=f"test.{node.moniker}.details", + ) + + total_supply += node_genesis_supply + + gentx_dir = self.command_node.data_dir / "config/gentx" + + os.makedirs(gentx_dir, exist_ok=True) + + for node in self.nodes: + node_gentx_dir = node.data_dir / "config/gentx" + for file_name in os.listdir(node_gentx_dir): + if file_name.endswith(".json"): + file_path = node_gentx_dir / file_name + dest_path = gentx_dir / file_name + shutil.move(file_path, dest_path) + + self.genesis.load() + self.genesis.edit( + { + "app_state": { + "bank": { + "supply": [ + { + "denom": BASE_DENOM, + "amount": total_supply.min_denom_amount_str(), + } + ], + }, + }, + } + ) + self.command_node.collect_gentxs(self.command_node.data_dir / "config/gentx") + self.command_node.validate_genesis() + self.genesis.load() + + ## configure nodes + for num, node in enumerate(self.nodes): + self.genesis.save_to(node.data_dir / "config/genesis.json") + node.load_config() + node.initial_configure_node() + node.config.apply_addr(node.node_addr) + + combine_seeds = self.combine_seeds() + for node in self.nodes: + node.config.edit({"p2p": {"persistent_peers": combine_seeds[node.moniker]}}) + + +async def main(): + chain_id = DEFAULT_TEST_CHAINID + g_network = GalaNetwork(3, chain_id=chain_id) + g_network.configure_network() + + print("start node...") + await g_network.start() + await asyncio.sleep(5) + + g_network.nodes[0].wait_for_block(5) + await g_network.stop() + + # await asyncio.sleep(1) + # await gn1.start() + # await asyncio.sleep(1) + # print(gn1.is_running()) + # gn1.wait_for_block(5, timeout=30) + # # await gn1.terminate() + # # await asyncio.sleep(10) + # if await gn1.terminate() == 0: + # print("Success") if __name__ == "__main__": diff --git a/tests/galacli/pyproject.toml b/tests/galacli/pyproject.toml index f8bf814..ffcf2bd 100644 --- a/tests/galacli/pyproject.toml +++ b/tests/galacli/pyproject.toml @@ -5,6 +5,7 @@ description = "Library for testing galacticad binary" readme = "README.md" requires-python = ">=3.12" dependencies = [ + "mnemonic>=0.21", "python-dateutil>=2.9.0.post0", "pyyaml>=6.0.2", "requests>=2.32.3", diff --git a/tests/galacli/uv.lock b/tests/galacli/uv.lock index daabf9b..2f8987d 100644 --- a/tests/galacli/uv.lock +++ b/tests/galacli/uv.lock @@ -54,6 +54,7 @@ name = "galacli" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "mnemonic" }, { name = "python-dateutil" }, { name = "pyyaml" }, { name = "requests" }, @@ -64,6 +65,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "mnemonic", specifier = ">=0.21" }, { name = "python-dateutil", specifier = ">=2.9.0.post0" }, { name = "pyyaml", specifier = ">=6.0.2" }, { name = "requests", specifier = ">=2.32.3" }, @@ -81,6 +83,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "mnemonic" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/77/e6232ed59fbd7b90208bb8d4f89ed5aabcf30a524bc2fb8f0dafbe8e7df9/mnemonic-0.21.tar.gz", hash = "sha256:1fe496356820984f45559b1540c80ff10de448368929b9c60a2b55744cc88acf", size = 152462 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/48/5abb16ce7f9d97b728e6b97c704ceaa614362e0847651f379ed0511942a0/mnemonic-0.21-py3-none-any.whl", hash = "sha256:72dc9de16ec5ef47287237b9b6943da11647a03fe7cf1f139fc3d7c4a7439288", size = 92701 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0"