stack-orchestrator/app/data/stacks/mainnet-laconic/deploy/commands.py

269 lines
11 KiB
Python
Raw Normal View History

# Copyright © 2022, 2023 Cerc
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
2023-07-24 02:54:05 +00:00
from app.util import get_yaml
2023-08-08 19:03:10 +00:00
from app.deploy_types import DeployCommandContext, LaconicStackSetupCommand
2023-07-24 02:54:05 +00:00
from app.stack_state import State
2023-07-30 04:38:46 +00:00
from app.deploy_util import VolumeMapping, run_container_command
2023-08-22 04:08:18 +00:00
from app.command_types import CommandOptions
2023-08-08 19:03:10 +00:00
from enum import Enum
from pathlib import Path
2023-08-22 23:43:03 +00:00
from shutil import copyfile, copytree
2023-08-22 04:08:18 +00:00
import json
2023-08-08 19:03:10 +00:00
import os
import sys
2023-08-09 03:06:44 +00:00
import tomli
2023-07-24 02:54:05 +00:00
default_spec_file_content = """config:
node_moniker: my-node-name
chain_id: my-chain-id
"""
init_help_text = """Add helpful text here on setting config variables.
"""
2023-07-24 02:54:05 +00:00
2023-08-08 19:03:10 +00:00
class SetupPhase(Enum):
INITIALIZE = 1
JOIN = 2
CREATE = 3
ILLEGAL = 3
2023-08-15 11:13:35 +00:00
def _config_toml_path(network_dir: Path):
return network_dir.joinpath("config","client.toml")
2023-08-22 03:11:30 +00:00
2023-08-15 11:13:35 +00:00
def _get_chain_id_from_config(network_dir: Path):
2023-08-09 03:06:44 +00:00
chain_id = None
2023-08-15 11:13:35 +00:00
with open(_config_toml_path(network_dir), "rb") as f:
2023-08-09 03:06:44 +00:00
toml_dict = tomli.load(f)
2023-08-09 14:20:08 +00:00
chain_id = toml_dict["chain-id"]
2023-08-09 03:06:44 +00:00
return chain_id
2023-08-15 11:13:35 +00:00
def _get_node_moniker_from_config(network_dir: Path):
2023-08-09 03:06:44 +00:00
moniker = None
2023-08-15 11:13:35 +00:00
with open(_config_toml_path(network_dir), "rb") as f:
2023-08-09 03:06:44 +00:00
toml_dict = tomli.load(f)
moniker = toml_dict["moniker"]
return moniker
2023-08-22 04:08:18 +00:00
def _get_node_key_from_gentx(options: CommandOptions, gentx_file_name: str):
gentx_file_path = Path(gentx_file_name)
if gentx_file_path.exists():
with open(Path(gentx_file_name), "rb") as f:
parsed_json = json.load(f)
return parsed_json['body']['messages'][0]['delegator_address']
else:
print(f"Error: gentx file: {gentx_file_name} does not exist")
sys.exit(1)
def _comma_delimited_to_list(list_str: str):
return list_str.split(",") if list_str else []
def _get_node_keys_from_gentx_files(options: CommandOptions, gentx_file_list: str):
node_keys = []
gentx_files = _comma_delimited_to_list(gentx_file_list)
for gentx_file in gentx_files:
node_key = _get_node_key_from_gentx(options, gentx_file)
if node_key:
node_keys.append(node_key)
return node_keys
def _copy_gentx_files(options: CommandOptions, network_dir: Path, gentx_file_list: str):
gentx_files = _comma_delimited_to_list(gentx_file_list)
for gentx_file in gentx_files:
gentx_file_path = Path(gentx_file)
copyfile(gentx_file_path, os.path.join(network_dir, "config", "gentx", os.path.basename(gentx_file_path)))
2023-08-08 19:03:10 +00:00
def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCommand, extra_args):
2023-08-22 04:08:18 +00:00
options = command_context.cluster_context.options
2023-08-23 03:51:37 +00:00
currency = "stake" # Does this need to be a parameter?
2023-08-22 04:08:18 +00:00
if options.debug:
print(f"parameters: {parameters}")
2023-08-08 19:03:10 +00:00
phase = SetupPhase.ILLEGAL
if parameters.initialize_network:
if parameters.join_network or parameters.create_network:
print("Can't supply --join-network or --create-network with --initialize-network")
sys.exit(1)
if not parameters.chain_id:
print("--chain-id is required")
sys.exit(1)
2023-08-09 03:06:44 +00:00
# node_moniker must be supplied
if not parameters.node_moniker:
print("Error: --node-moniker is required")
sys.exit(1)
2023-08-08 19:03:10 +00:00
phase = SetupPhase.INITIALIZE
elif parameters.join_network:
if parameters.initialize_network or parameters.create_network:
print("Can't supply --initialize-network or --create-network with --join-network")
sys.exit(1)
phase = SetupPhase.JOIN
elif parameters.create_network:
if parameters.initialize_network or parameters.join_network:
print("Can't supply --initialize-network or --join-network with --create-network")
sys.exit(1)
phase = SetupPhase.CREATE
2023-08-09 14:17:30 +00:00
network_dir = Path(parameters.network_dir).absolute()
laconicd_home_path_in_container = "/laconicd-home"
mounts = [
VolumeMapping(network_dir, laconicd_home_path_in_container)
]
2023-08-08 19:03:10 +00:00
if phase == SetupPhase.INITIALIZE:
2023-08-09 14:17:30 +00:00
2023-08-08 19:03:10 +00:00
# We want to create the directory so if it exists that's an error
if os.path.exists(network_dir):
print(f"Error: network directory {network_dir} already exists")
sys.exit(1)
2023-08-09 03:06:44 +00:00
2023-08-08 19:03:10 +00:00
os.mkdir(network_dir)
2023-08-09 14:17:30 +00:00
2023-08-08 19:03:10 +00:00
output, status = run_container_command(
2023-08-09 14:17:30 +00:00
command_context,
2023-08-22 22:44:19 +00:00
"laconicd", f"laconicd init {parameters.node_moniker} --home {laconicd_home_path_in_container}\
--chain-id {parameters.chain_id}", mounts)
2023-08-22 23:43:03 +00:00
if options.debug:
print(f"Command output: {output}")
2023-08-08 19:03:10 +00:00
elif phase == SetupPhase.JOIN:
if not os.path.exists(network_dir):
print(f"Error: network directory {network_dir} doesn't exist")
sys.exit(1)
2023-08-09 03:06:44 +00:00
# Get the chain_id from the config file created in the INITIALIZE phase
2023-08-15 11:13:35 +00:00
chain_id = _get_chain_id_from_config(network_dir)
2023-08-09 14:17:30 +00:00
2023-08-08 19:03:10 +00:00
output1, status1 = run_container_command(
2023-08-22 22:44:19 +00:00
command_context, "laconicd", f"laconicd keys add {parameters.key_name} --home {laconicd_home_path_in_container}\
--keyring-backend test", mounts)
2023-08-22 23:43:03 +00:00
if options.debug:
print(f"Command output: {output1}")
2023-08-08 19:03:10 +00:00
output2, status2 = run_container_command(
2023-08-22 23:43:03 +00:00
command_context,
2023-08-08 19:03:10 +00:00
"laconicd",
2023-08-23 03:51:37 +00:00
f"laconicd add-genesis-account {parameters.key_name} 12900000000000000000000{currency}\
2023-08-22 22:44:19 +00:00
--home {laconicd_home_path_in_container} --keyring-backend test",
2023-08-08 19:03:10 +00:00
mounts)
2023-08-22 03:11:30 +00:00
print(f"Command output: {output2}")
2023-08-08 19:03:10 +00:00
output3, status3 = run_container_command(
2023-08-22 23:43:03 +00:00
command_context,
2023-08-08 19:03:10 +00:00
"laconicd",
2023-08-23 03:51:37 +00:00
f"laconicd gentx {parameters.key_name} 90000000000{currency} --home {laconicd_home_path_in_container}\
2023-08-22 22:44:19 +00:00
--chain-id {chain_id} --keyring-backend test",
2023-08-08 19:03:10 +00:00
mounts)
print(f"Command output: {output3}")
2023-08-22 03:11:30 +00:00
output4, status4 = run_container_command(
command_context,
"laconicd",
f"laconicd keys show {parameters.key_name} -a --home {laconicd_home_path_in_container} --keyring-backend test",
mounts)
print(f"Command output: {output4}")
2023-08-08 19:03:10 +00:00
elif phase == SetupPhase.CREATE:
if not os.path.exists(network_dir):
print(f"Error: network directory {network_dir} doesn't exist")
sys.exit(1)
2023-08-09 14:17:30 +00:00
2023-08-22 21:20:41 +00:00
# In the CREATE phase, we are either a "coordinator" node, generating the genesis.json file ourselves
# OR we are a "not-coordinator" node, consuming a genesis file we got from the coordinator node.
if parameters.genesis_file:
# We got the genesis file from elsewhere
# Copy it into our network dir
genesis_file_path = Path(parameters.genesis_file)
if not os.path.exists(genesis_file_path):
print(f"Error: supplied genesis file: {parameters.genesis_file} does not exist.")
sys.exit(1)
copyfile(genesis_file_path, os.path.join(network_dir, "config", os.path.basename(genesis_file_path)))
else:
# We're generating the genesis file
# First look in the supplied gentx files for the other nodes' keys
other_node_keys = _get_node_keys_from_gentx_files(options, parameters.gentx_file_list)
# Add those keys to our genesis, with balances we determine here (why?)
for other_node_key in other_node_keys:
outputk, statusk = run_container_command(
2023-08-23 03:51:37 +00:00
command_context, "laconicd", f"laconicd add-genesis-account {other_node_key} 12900000000000000000000{currency}\
2023-08-22 21:20:41 +00:00
--home {laconicd_home_path_in_container} --keyring-backend test", mounts)
print(f"Command output: {outputk}")
# Copy the gentx json files into our network dir
_copy_gentx_files(options, network_dir, parameters.gentx_file_list)
# Now we can run collect-gentxs
output1, status1 = run_container_command(
command_context, "laconicd", f"laconicd collect-gentxs --home {laconicd_home_path_in_container}", mounts)
print(f"Command output: {output1}")
2023-08-22 23:43:03 +00:00
print(f"Generated genesis file, please copy to other nodes as required: \
{os.path.join(network_dir, 'config', 'genesis.json')}")
2023-08-22 21:20:41 +00:00
# In both cases we validate the genesis file now
2023-08-08 19:03:10 +00:00
output2, status1 = run_container_command(
2023-08-09 14:17:30 +00:00
command_context, "laconicd", f"laconicd validate-genesis --home {laconicd_home_path_in_container}", mounts)
2023-08-08 19:03:10 +00:00
print(f"Command output: {output2}")
2023-08-22 23:43:03 +00:00
2023-08-08 19:03:10 +00:00
else:
print("Illegal parameters supplied")
sys.exit(1)
2023-08-22 23:43:03 +00:00
def create(command_context: DeployCommandContext, extra_args):
network_dir = extra_args
if network_dir is None:
print("Error: --network-dir must be supplied")
sys.exit(1)
network_dir_path = Path(network_dir)
if not (network_dir_path.exists() and network_dir_path.is_dir()):
print(f"Error: supplied network directory does not exist: {network_dir}")
sys.exit(1)
2023-08-23 03:22:38 +00:00
config_dir_path = network_dir_path.joinpath("config")
if not (config_dir_path.exists() and config_dir_path.is_dir()):
print(f"Error: supplied network directory does not contain a config directory: {config_dir_path}")
sys.exit(1)
data_dir_path = network_dir_path.joinpath("data")
if not (data_dir_path.exists() and data_dir_path.is_dir()):
print(f"Error: supplied network directory does not contain a data directory: {data_dir_path}")
sys.exit(1)
2023-08-22 23:43:03 +00:00
# Copy the network directory contents into our deployment
# TODO: change this to work with non local paths
deployment_config_dir = command_context.deployment_dir.joinpath("data", "laconicd-config")
2023-08-23 03:22:38 +00:00
copytree(config_dir_path, deployment_config_dir, dirs_exist_ok=True)
# Copy the data directory contents into our deployment
# TODO: change this to work with non local paths
deployment_data_dir = command_context.deployment_dir.joinpath("data", "laconicd-data")
copytree(data_dir_path, deployment_data_dir, dirs_exist_ok=True)
2023-07-24 02:54:05 +00:00
2023-07-30 04:38:46 +00:00
def init(command_context: DeployCommandContext):
2023-07-24 02:54:05 +00:00
print(init_help_text)
yaml = get_yaml()
return yaml.load(default_spec_file_content)
2023-07-30 04:38:46 +00:00
def get_state(command_context: DeployCommandContext):
2023-07-24 02:54:05 +00:00
print("Here we get state")
return State.CONFIGURED
2023-07-30 04:38:46 +00:00
def change_state(command_context: DeployCommandContext):
2023-07-24 02:54:05 +00:00
pass