diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2948191 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*-deployment +*-spec.yml + +# Playbook vars +playbooks/first-validator/first-validator-vars.yml diff --git a/README.md b/README.md index 450b5e3..c870d42 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# mainnet-laconicd-stack +# laconicd-stack + +- Follow [run-first-validator.md](run-first-validator.md) to run the first validator node diff --git a/genesis/.gitkeep b/genesis/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/node-addresses.yml b/node-addresses.yml new file mode 100644 index 0000000..5ff7b09 --- /dev/null +++ b/node-addresses.yml @@ -0,0 +1,4 @@ +# Add your node addresses here +# Example: +# - node-1-id@node-1-host:26656 +# - node-2-id@node-2-host:26656 diff --git a/playbooks/README.md b/playbooks/README.md new file mode 100644 index 0000000..679dd14 --- /dev/null +++ b/playbooks/README.md @@ -0,0 +1,42 @@ +# Ansible Installation + +- Install [Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#installing-and-upgrading-ansible-with-pip) + +- Add location of the directory containing the ansible binary to your `PATH` + +- Set Locale Encoding to `UTF-8` + + Ansible requires the locale encoding to be `UTF-8`. You can either use the `LANG` prefix when running Ansible commands or set the system-wide locale + + - Option 1: Use `LANG` Prefix in Commands + + If you prefer not to change the system-wide locale, you can use the `LANG` prefix when running Ansible commands: + + ```bash + LANG=en_US.UTF-8 ansible-playbook your_playbook.yml + ``` + + - Option 2: Set System-Wide Locale + + - Edit the `/etc/default/locale` file: + + ```bash + sudo nano /etc/default/locale + ``` + + - Set the `LANG` variable to en_US.UTF-8: + + ```bash + LANG="en_US.UTF-8" + ``` + + - Reboot your system or log out and log back in to apply the changes + + - Reference: + +- Verify ansible installation by running the following command: + + ```bash + ansible --version + # ansible [core 2.17.2] + ``` diff --git a/playbooks/first-validator/first-validator-vars.example.yml b/playbooks/first-validator/first-validator-vars.example.yml new file mode 100644 index 0000000..6d8b34f --- /dev/null +++ b/playbooks/first-validator/first-validator-vars.example.yml @@ -0,0 +1,7 @@ +cerc_moniker: "LaconicMainnetNode" +cerc_chain_id: "laconic-mainnet" +min_gas_price: 0.001 +cerc_loglevel: "info" +key_name: "validator" +pvt_key: "" +genesis_file: diff --git a/playbooks/first-validator/generate-genesis.yml b/playbooks/first-validator/generate-genesis.yml new file mode 100644 index 0000000..d265fd2 --- /dev/null +++ b/playbooks/first-validator/generate-genesis.yml @@ -0,0 +1,44 @@ +--- +- name: Generate Mainnet Genesis File + hosts: localhost + vars_files: + - first-validator-vars.yml + connection: local + tasks: + - name: Fetch repositories + ansible.builtin.shell: + cmd: "laconic-so --stack ~/cerc/laconicd-stack/stack-orchestrator/stacks/mainnet-laconicd setup-repositories --git-ssh --pull" + + - name: Build containers + ansible.builtin.shell: + cmd: "laconic-so --stack ~/cerc/laconicd-stack/stack-orchestrator/stacks/mainnet-laconicd build-containers" + + - name: Copy exported testnet state file + ansible.builtin.copy: + src: "{{ exported_state_path }}" + dest: "~/cerc/laconicd-stack/testnet-state.json" + remote_src: true # Set to true if exported_state_path is on the target host + + - block: + - name: Run script to generate genesis file + ansible.builtin.shell: + cmd: "CHAIN_ID={{ cerc_chain_id }} EARLY_SUPPORTS_ACC_ADDRESS={{ early_supports_acc_address }} {{ ansible_env.HOME }}/cerc/laconicd-stack/scripts/generate-mainnet-genesis.sh {{ ansible_env.HOME }}/cerc/laconicd-stack/testnet-state.json" + chdir: "{{ lookup('env', 'PWD') }}" + always: + - name: Clean up temporary genesis directory + ansible.builtin.file: + path: "{{ ansible_env.HOME }}/cerc/laconicd-stack/playbooks/first-validator/mainnet-genesis" + state: absent + + - name: Remove temporary copied state file + ansible.builtin.file: + path: "~/cerc/laconicd-stack/testnet-state.json" + state: absent + + - name: Display genesis file location + ansible.builtin.debug: + msg: "Mainnet genesis file generated at output/genesis.json" + + - name: Display staking-amount file location + ansible.builtin.debug: + msg: "Staking amount written to output/staking-amount.json" diff --git a/playbooks/first-validator/run-first-validator.yml b/playbooks/first-validator/run-first-validator.yml new file mode 100644 index 0000000..8022454 --- /dev/null +++ b/playbooks/first-validator/run-first-validator.yml @@ -0,0 +1,92 @@ +--- +- name: Run mainnet validator node + hosts: localhost + vars_files: + - first-validator-vars.yml + vars: + data_directory: "{{ lookup('env', 'DATA_DIRECTORY') }}" + mainnet_deployment_dir: "{{ lookup('env', 'MAINNET_DEPLOYMENT_DIR') }}" + spec_file: "{{data_directory}}/laconicd-spec.yml" + spec_template: "./templates/specs/spec-template.yml.j2" + tasks: + - name: Fail if DATA_DIRECTORY or MAINNET_DEPLOYMENT_DIR env vars are not set + fail: + msg: >- + Required environment variables are not set. + Please export both DATA_DIRECTORY and MAINNET_DEPLOYMENT_DIR before running the playbook. + when: lookup('env', 'DATA_DIRECTORY') == '' or lookup('env', 'MAINNET_DEPLOYMENT_DIR') == '' + + - name: Fail if required key files are not defined + fail: + msg: >- + Required key files are not defined. + Please set genesis_file in first-validator-vars.yml. + when: not genesis_file + - name: Fetch laconicd stack + shell: laconic-so fetch-stack git.vdb.to/cerc-io/laconicd-stack --git-ssh --pull + + - name: Setup required repositories + shell: > + laconic-so --stack ~/cerc/laconicd-stack/stack-orchestrator/stacks/mainnet-laconicd + setup-repositories --git-ssh --pull + + - name: Build container images + shell: | + laconic-so --stack ~/cerc/laconicd-stack/stack-orchestrator/stacks/mainnet-laconicd build-containers + + - name: Create deployment spec file + shell: | + laconic-so --stack ~/cerc/laconicd-stack/stack-orchestrator/stacks/mainnet-laconicd deploy init --output {{ spec_file }} + + - name: Replace network section in spec_file + shell: > + yq eval '(.network) = load("{{ spec_template }}").network' -i {{ spec_file }} + + - name: Create deployment from spec file + shell: | + laconic-so --stack ~/cerc/laconicd-stack/stack-orchestrator/stacks/mainnet-laconicd deploy create --spec-file {{ spec_file }} --deployment-dir {{data_directory}}/{{ mainnet_deployment_dir }} + + - name: Create config.env + copy: + dest: "{{data_directory}}/{{ mainnet_deployment_dir }}/config.env" + content: | + CERC_MONIKER: "{{ cerc_moniker }}" + CERC_CHAIN_ID: "{{ cerc_chain_id }}" + MIN_GAS_PRICE: "{{ min_gas_price }}" + CERC_LOGLEVEL: "{{ cerc_loglevel }}" + KEY_NAME: "{{ key_name }}" + mode: '0777' + + - name: Ensure tmp directory exists inside laconicd-data + file: + path: "{{data_directory}}/{{ mainnet_deployment_dir }}/data/laconicd-data/tmp" + state: directory + mode: '0755' + + - name: Copy genesis file to laconicd-data tmp directory + copy: + src: "{{ genesis_file }}" + dest: "{{data_directory}}/{{ mainnet_deployment_dir }}/data/laconicd-data/tmp/genesis.json" + mode: '0644' + + - name: Fail if pvt_key is not set + fail: + msg: >- + Private key (pvt_key) is not set in first-validator-vars.yml. + This is required for creating the gentx. + when: not pvt_key + + - name: Run script to create and collect gentx + shell: | + docker run -i \ + -v {{data_directory}}/{{ mainnet_deployment_dir }}/data/laconicd-data:/root/.laconicd \ + -v {{data_directory}}/{{ mainnet_deployment_dir }}/config/mainnet-laconicd:/scripts \ + -e "PVT_KEY={{ pvt_key }}" \ + -e "KEY_NAME={{ key_name }}" \ + -e "CERC_MONIKER={{ cerc_moniker }}" \ + -e "CERC_CHAIN_ID={{ cerc_chain_id }}" \ + cerc/laconicd:local bash -c "/scripts/create-and-collect-gentx.sh" + + - name: Run validator node + shell: | + laconic-so deployment --dir {{data_directory}}/{{ mainnet_deployment_dir }} start diff --git a/playbooks/first-validator/templates/specs/spec-template.yml.j2 b/playbooks/first-validator/templates/specs/spec-template.yml.j2 new file mode 100644 index 0000000..9ae553e --- /dev/null +++ b/playbooks/first-validator/templates/specs/spec-template.yml.j2 @@ -0,0 +1,9 @@ +network: + ports: + laconicd: + - '6060:6060' + - '26657:26657' + - '26656:26656' + - '9473:9473' + - '9090:9090' + - '1317:1317' diff --git a/run-first-validator.md b/run-first-validator.md new file mode 100644 index 0000000..ffb5d0a --- /dev/null +++ b/run-first-validator.md @@ -0,0 +1,116 @@ +# Run First Validator Node + +## Prerequisites + +- [ansible](playbooks/README.md#ansible-installation) +- [laconic-so](https://github.com/cerc-io/stack-orchestrator/?tab=readme-ov-file#install) + +## Generate mainnet genesis file + +- Fetch the stack in machine where the testnet chain node is running + + ```bash + laconic-so fetch-stack git.vdb.to/cerc-io/laconicd-stack --git-ssh --pull + ``` + +- Run script to export state from testnet chain + + ```bash + ~/cerc/laconicd-stack/scripts/export-testnet-state.sh + ``` + + - The file will be generated in `/export/testnet-state.json` + +- If mainnet node will be setup in new machine, fetch the stack again + + ```bash + laconic-so fetch-stack git.vdb.to/cerc-io/laconicd-stack --git-ssh --pull + ``` + +- Copy over the exported `testnet-state.json` file to target machine + +- Set envs + + ```bash + export EXPORTED_STATE_PATH= + export EARLY_SUPPORTS_ACC_ADDR= + ``` + +- Run playbook to use exported state for generating mainnet genesis + + ```bash + ansible-playbook -i localhost, -c local ~/cerc/laconicd-stack/playbooks/first-validator/generate-genesis.yml -e "exported_state_path=$EXPORTED_STATE_PATH" -e "early_supports_acc_address=$EARLY_SUPPORTS_ACC_ADDR" + ``` + +- Genesis file will be generated in output directory along with a file specifying the staking amount + + ```bash + # List files in output directory - genesis.json and staking-amount.json + ls -l output + ``` + +## Run node + + + +- Copy the example variables file: + ```bash + cp ~/cerc/laconicd-stack/playbooks/first-validator/first-validator-vars.example.yml ~/cerc/laconicd-stack/playbooks/first-validator/first-validator-vars.yml + ``` + +- Update `~/cerc/laconicd-stack/playbooks/first-validator/first-validator-vars.yml` with required values: + + ```bash + # Private key of the existing account in hex format (required for gentx) + pvt_key: "" + + # Path to the generated mainnet genesis file generated in the previous step + genesis_file: "" + + # Set custom moniker for the node + cerc_moniker: "LaconicMainnetNode" + + # Set desired key name + key_name: "validator" + ``` + +- Export the data directory and mainnet deployment directory as environment variables: + + ```bash + # Parent directory where the deployment directory will live + export DATA_DIRECTORY= + + # Set mainnet deployment directory + # for eg: mainnet-laconicd-deployment + export MAINNET_DEPLOYMENT_DIR= + ``` + +- Run ansible playbook to submit the gentx and run the node: + + ```bash + ansible-playbook -i localhost, -c local playbooks/first-validator/run-first-validator.yml + ``` + +- Check logs to ensure that node is running: + + ```bash + laconic-so deployment --dir $DATA_DIRECTORY/$MAINNET_DEPLOYMENT_DIR logs laconicd -f + ``` + +## Publish Genesis File and Node Address + +- Copy the genesis file to [genesis](./genesis) folder: + + ```bash + sudo cp $DATA_DIRECTORY/$MAINNET_DEPLOYMENT_DIR/data/laconicd-data/config/genesis.json ./genesis/mainnet-genesis.json + ``` + +- Get your node's address: + + ```bash + laconic-so deployment --dir $DATA_DIRECTORY/$MAINNET_DEPLOYMENT_DIR exec laconicd 'echo $(laconicd cometbft show-node-id)@YOUR_PUBLIC_IP_ADDRESS:26656' + ``` + +- Add your node's address to [node-addresses.yml](./node-addresses.yml) + +- Submit a PR with this genesis file and node address so that it is available to other validators diff --git a/scripts/export-testnet-state.sh b/scripts/export-testnet-state.sh new file mode 100755 index 0000000..a6b21d4 --- /dev/null +++ b/scripts/export-testnet-state.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Note: Needs to be run in a docker container with image testnet cerc/laconicd:local + +# Exit on error +set -e +set -u + +# Check args +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +TESTNET_DEPLOYMENT_DIR="$1" + +# Create a temporary target directory +OUTPUT_DIR=${TESTNET_DEPLOYMENT_DIR}/export +mkdir -p $OUTPUT_DIR + +# Export state from testnet chain +testnet_state_file="$OUTPUT_DIR/testnet-state.json" +docker run -it \ + -v ${TESTNET_DEPLOYMENT_DIR}/data/laconicd-data:/root/testnet-deployment/.laconicd \ + cerc/laconicd:local bash -c "laconicd export --home /root/testnet-deployment/.laconicd" \ + | jq > "$testnet_state_file" + +echo "Exported state from testnet to $testnet_state_file" diff --git a/scripts/generate-mainnet-genesis.sh b/scripts/generate-mainnet-genesis.sh new file mode 100755 index 0000000..e1e4982 --- /dev/null +++ b/scripts/generate-mainnet-genesis.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Exit on error +set -e +set -u + +# Check args +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +TESTNET_STATE_FILE="$1" +MAINNET_GENESIS_DIR=mainnet-genesis +OUTPUT_DIR=output + +# Create a required target directories +mkdir -p $MAINNET_GENESIS_DIR +mkdir -p $OUTPUT_DIR + +# -------- + +# Copy testnet state file to required dir +cp $TESTNET_STATE_FILE $MAINNET_GENESIS_DIR/testnet-state.json + +# -------- + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "Initializing a new empty chain with chain-id $CHAIN_ID..." +docker run \ + -v ./$MAINNET_GENESIS_DIR:/root/.laconicd \ + -v $script_dir:/scripts \ + -e "CHAIN_ID=$CHAIN_ID" \ + cerc/laconicd:local bash -c "/scripts/init-mainnet.sh" + +# -------- + +# Install required bech32 dependency +# TODO: Avoid installing bech32 system-wide +python3 -m pip install bech32 --break-system-packages + +# Carry over state from testnet to mainnet +echo "Carrying over state from testnet state to mainnet genesis..." +python3 $script_dir/transfer-state.py + +# -------- + +# Run a script with cerc/laconicd:local to generate the genesis file +# with onboarding module state and given allocations +echo "Performing alps allocations..." +docker run \ + -v ./$MAINNET_GENESIS_DIR:/root/.laconicd \ + -v $script_dir:/scripts \ + -e "CHAIN_ID=$CHAIN_ID" \ + -e "EARLY_SUPPORTS_ACC_ADDRESS=$EARLY_SUPPORTS_ACC_ADDRESS" \ + cerc/laconicd:local bash -c "/scripts/genesis.sh" + +# Copy over the genesis file to output folder +cp ./$MAINNET_GENESIS_DIR/config/genesis.json $OUTPUT_DIR/genesis.json + +echo "Genesis file for mainnet written to $OUTPUT_DIR/genesis.json" + +# -------- + +# Clean up +rm -rf $MAINNET_GENESIS_DIR diff --git a/scripts/genesis.sh b/scripts/genesis.sh new file mode 100755 index 0000000..da012c7 --- /dev/null +++ b/scripts/genesis.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# Exit on error +set -e +set -u + +# Note: Needs to be run in a docker container with image cerc/laconicd:local + +KEYRING="test" +NODE_HOME="/root/.laconicd" + +EARLY_SUPPORTS_ACC_ADDRESS=${EARLY_SUPPORTS_ACC_ADDRESS} + +if [ -z "$EARLY_SUPPORTS_ACC_ADDRESS" ]; then + echo "EARLY_SUPPORTS_ACC_ADDRESS not provided, exiting..." + exit 1 +fi + +# alps allocations +# Total supply: 129600 * 10^18 alps +EARLY_SUPPORTS_ALLOC="12960000000000000000000" # Early supports: 12960 * 10^18 alps (10% of total supply) +LOCKUP_ALLOC="116640000000000000000000" # Lockup: 116640 * 10^18 alps (90% of total supply) +LPS_LOCKUP_MODULE_ACCOUNT="lps_lockup" +LPS_DENOM="alps" + +testnet_state_file="$NODE_HOME/testnet-state.json" +mainnet_genesis_file="$NODE_HOME/config/genesis.json" + +# Update any module params if required here +update_genesis() { + jq "$1" $NODE_HOME/config/genesis.json > $NODE_HOME/config/tmp_genesis.json && + mv $NODE_HOME/config/tmp_genesis.json $NODE_HOME/config/genesis.json +} + +# Set distribution community tax to 1 for disabling staking rewards and redirect them to the community pool +update_genesis '.app_state["distribution"]["params"]["community_tax"]="1.000000000000000000"' + +# Increase threshold in gov proposals for making it difficult to spend community pool funds +echo "Setting high threshold for accepting governance proposal" +update_genesis '.app_state["gov"]["params"]["quorum"]="1.000000000000000000"' +# Set expedited threshold to 100% +update_genesis '.app_state["gov"]["params"]["expedited_threshold"]="1.000000000000000000"' +# Set normal threshold to 99% since it needs to be lesser than expedited threshold +update_genesis '.app_state["gov"]["params"]["threshold"]="0.990000000000000000"' + +# Perform alps allocations +laconicd genesis add-genesis-account $EARLY_SUPPORTS_ACC_ADDRESS $EARLY_SUPPORTS_ALLOC$LPS_DENOM --keyring-backend $KEYRING --append + +# Use zero address to add an account for lps_lockup +zero_address="laconic1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqklcls0" +laconicd genesis add-genesis-account $zero_address $LOCKUP_ALLOC$LPS_DENOM --keyring-backend $KEYRING --module-name $LPS_LOCKUP_MODULE_ACCOUNT + +# Update the lps_lockup address in bank module state +lps_lockup_address=$(jq -r '.app_state.auth.accounts[] | select(.name == "lps_lockup") | .base_account.address' $HOME/.laconicd/config/genesis.json) +jq --arg old "$zero_address" --arg new "$lps_lockup_address" \ + '.app_state.bank.balances |= map(if .address == $old then .address = $new else . end)' "$mainnet_genesis_file" > tmp.$$.json \ + && mv tmp.$$.json "$mainnet_genesis_file" +jq '(.app_state.auth.accounts[] | select(.name == "lps_lockup") | .permissions) = []' "$mainnet_genesis_file" > tmp.$$.json \ + && mv tmp.$$.json "$mainnet_genesis_file" + +# TODO: Dump JSON for allocations in LPS_LOCKUP_MODULE_ACCOUNT state + +# Ensure that resulting genesis file is valid +laconicd genesis validate diff --git a/scripts/init-mainnet.sh b/scripts/init-mainnet.sh new file mode 100755 index 0000000..a6a0c01 --- /dev/null +++ b/scripts/init-mainnet.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Exit on error +set -e +set -u + +# Note: Needs to be run in a docker container with image cerc/laconicd:local + +CHAIN_ID=${CHAIN_ID:-"laconic-mainnet"} +MONIKER=${MONIKER:-"mainnet-node"} +NODE_HOME="/root/.laconicd" + +laconicd config set client chain-id $CHAIN_ID +laconicd init $MONIKER --chain-id $CHAIN_ID --default-denom alnt + +# Allow all permissions to process further +chmod -R 777 $NODE_HOME/data $NODE_HOME/config diff --git a/scripts/transfer-state.py b/scripts/transfer-state.py new file mode 100644 index 0000000..a8081f7 --- /dev/null +++ b/scripts/transfer-state.py @@ -0,0 +1,165 @@ +import json + +from decimal import Decimal +from collections import defaultdict +from bech32 import bech32_decode, bech32_encode, convertbits + +#------ + +# Helper methods + +def valoper_to_account_address(valoper_addr: str, new_prefix = "laconic") -> str: + hrp, data = bech32_decode(valoper_addr) + if hrp is None or data is None: + raise ValueError("Invalid bech32 address") + + # Convert back from 5-bit to 8-bit + decoded = convertbits(data, 5, 8, False) + if decoded is None: + raise ValueError("Failed to convert bits") + + # Re-encode with new prefix + converted_data = convertbits(decoded, 8, 5, True) + return bech32_encode(new_prefix, converted_data) + +def get_min_delegation_share(state): + validators = state["app_state"]["staking"]["validators"] + delegations = state["app_state"]["staking"]["delegations"] + + shares_list = [] + + for val in validators: + valoper_addr = val["operator_address"] + orig_delegator_addr = valoper_to_account_address(valoper_addr) + + # Find delegations where the delegator is the original delegator and delegated to their validator + for delegation in delegations: + if (delegation["delegator_address"] == orig_delegator_addr and + delegation["validator_address"] == valoper_addr): + shares = int(Decimal(delegation["shares"])) + shares_list.append(shares) + + break + + # Find minimum + return min(shares_list) + +#------ + +# Read testnet and mainnet states + +mainnet_genesis_dir="mainnet-genesis" +testnet_state_file=f"{mainnet_genesis_dir}/testnet-state.json" +mainnet_genesis_file=f"{mainnet_genesis_dir}/config/genesis.json" +staking_amount_file=f"output/staking-amount.json" + +with open(testnet_state_file) as f: + testnet_state = json.load(f) + +with open(mainnet_genesis_file) as f: + mainnet_state = json.load(f) + +#------ + +# Import required module state +mainnet_state["app_state"]["auth"] = testnet_state["app_state"]["auth"] +mainnet_state["app_state"]["bond"] = testnet_state["app_state"]["bond"] +mainnet_state["app_state"]["bank"] = testnet_state["app_state"]["bank"] +mainnet_state["app_state"]["registry"] = testnet_state["app_state"]["registry"] +mainnet_state["app_state"]["slashing"]["params"] = testnet_state["app_state"]["slashing"]["params"] +mainnet_state["consensus"]["params"] = testnet_state["consensus"]["params"] + +#------ + +# Allocate back delegation stakes + +# Build map of address -> extra balance to add (as integers) +deltas = {} +for delegation in testnet_state["app_state"]["staking"]["delegations"]: + addr = delegation["delegator_address"] + amount = int(Decimal(delegation["shares"])) + deltas[addr] = deltas.get(addr, 0) + amount + +# Now apply the deltas to existing balances +supply_increment = 0 +for balance in mainnet_state["app_state"]["bank"]["balances"]: + addr = balance["address"] + if addr in deltas: + for coin in balance["coins"]: + if coin["denom"] == "alnt": + coin["amount"] = str(int(coin["amount"]) + deltas[addr]) + supply_increment += deltas[addr] + break + del deltas[addr] + +# Increase the total supply +for coin in mainnet_state["app_state"]["bank"]["supply"]: + if coin["denom"] == "alnt": + coin["amount"] = str(int(coin["amount"]) + supply_increment) + +#------ + +# Remove non-required module accounts + +# Addresses to remove +addresses_to_remove = { + "bonded_tokens_pool", + "not_bonded_tokens_pool", + "distribution" +} + +# Remove from auth.accounts and get their addresses +removed_addresses = set() +new_accounts = [] +for account in mainnet_state["app_state"]["auth"]["accounts"]: + account_type = account.get("@type", "") + if "ModuleAccount" in account_type and account.get("name") in addresses_to_remove: + removed_addresses.add(account["base_account"]["address"]) + continue + new_accounts.append(account) + +mainnet_state["app_state"]["auth"]["accounts"] = new_accounts + +# Remove from bank.balances and tally removed amounts +new_balances = [] +removed_amounts = defaultdict(int) + +for bal in mainnet_state["app_state"]["bank"]["balances"]: + if bal["address"] in removed_addresses: + # Skip this account + for coin in bal["coins"]: + denom = coin["denom"] + amount = int(coin["amount"]) + removed_amounts[denom] += amount + + continue + + new_balances.append(bal) + +mainnet_state["app_state"]["bank"]["balances"] = new_balances + +# Reduce from bank supply +new_supply = [] +for coin in mainnet_state["app_state"]["bank"]["supply"]: + denom = coin["denom"] + amount = int(coin["amount"]) + amount -= removed_amounts.get(denom, 0) + new_supply.append({ + "denom": denom, + "amount": str(amount) + }) + +mainnet_state["app_state"]["bank"]["supply"] = new_supply + +#------ + +# Find minimum delegation share for common staking amount + +min_delegation_share = get_min_delegation_share(testnet_state) +with open(staking_amount_file, "w") as f: + json.dump({"common_staking_amount": min_delegation_share}, f, indent=2) +print(f"Staking amount written to {staking_amount_file}") + +# Write back modified state +with open(mainnet_genesis_file, "w") as f: + json.dump(mainnet_state, f, indent=2) diff --git a/stack-orchestrator/compose/docker-compose-mainnet-laconicd.yml b/stack-orchestrator/compose/docker-compose-mainnet-laconicd.yml new file mode 100644 index 0000000..6822d82 --- /dev/null +++ b/stack-orchestrator/compose/docker-compose-mainnet-laconicd.yml @@ -0,0 +1,32 @@ +services: + laconicd: + restart: unless-stopped + image: cerc/laconicd:local + command: ["bash", "-c", "/opt/run-laconicd.sh"] + environment: + CERC_MONIKER: ${CERC_MONIKER} + CERC_CHAIN_ID: ${CERC_CHAIN_ID:-laconic-mainnet} + CERC_PEERS: ${CERC_PEERS} + MIN_GAS_PRICE: ${MIN_GAS_PRICE:-0.001} + CERC_LOGLEVEL: ${CERC_LOGLEVEL:-info} + volumes: + - laconicd-data:/root/.laconicd + - ../config/mainnet-laconicd/run-laconicd.sh:/opt/run-laconicd.sh + ports: + - "6060" + - "26657" + - "26656" + - "9473" + - "9090" + - "1317" + healthcheck: + test: ["CMD", "nc", "-vz", "127.0.0.1", "26657"] + interval: 30s + timeout: 10s + retries: 10 + start_period: 10s + extra_hosts: + - "host.docker.internal:host-gateway" + +volumes: + laconicd-data: diff --git a/stack-orchestrator/config/mainnet-laconicd/create-and-collect-gentx.sh b/stack-orchestrator/config/mainnet-laconicd/create-and-collect-gentx.sh new file mode 100755 index 0000000..f213846 --- /dev/null +++ b/stack-orchestrator/config/mainnet-laconicd/create-and-collect-gentx.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +set -e + +NODE_HOME=/root/.laconicd +genesis_file_path=$NODE_HOME/config/genesis.json +KEYRING="test" + +if [ -f "$genesis_file_path" ]; then + echo "Genesis file already created, exiting..." + exit 0 +fi + +if [ -z "$PVT_KEY" ]; then + echo "PVT_KEY environment variable not set, exiting..." + exit 1 +fi + +if [ -z "$KEY_NAME" ]; then + echo "KEY_NAME environment variable not set, exiting..." + exit 1 +fi + +input_genesis_file=$NODE_HOME/tmp/genesis.json +if [ ! -f ${input_genesis_file} ]; then + echo "Genesis file not provided, exiting..." + exit 1 +fi + +DENOM=alnt + +# Strip leading and trailing quotes ("") if they exist +export MONIKER=$(echo "$CERC_MONIKER" | sed -e 's/^["'\'']//g' -e 's/["'\'']$//g') +export CHAIN_ID=$(echo "$CERC_CHAIN_ID" | sed -e 's/^["'\'']//g' -e 's/["'\'']$//g') + +# Init +laconicd config set client chain-id $CHAIN_ID --home $NODE_HOME +laconicd config set client keyring-backend $KEYRING +laconicd init $MONIKER --chain-id=$CHAIN_ID --home $NODE_HOME + +# Copy over provided genesis config +cp $input_genesis_file $genesis_file_path + +# Import private key +laconicd keys import-hex "$KEY_NAME" "$PVT_KEY" --keyring-backend $KEYRING + +# Get account address corresponding to the imported key +account_address=$(laconicd keys show "$KEY_NAME" --keyring-backend "$KEYRING" | grep 'address:' | awk -F': ' '{print $2}' | xargs) + +if [ -z "$account_address" ]; then + echo "Failed to get account address for key name $KEY_NAME, exiting..." + laconicd keys list --keyring-backend $KEYRING + exit 1 +fi + +# TODO: Use staking amount from output/staking-amount.json +# Get balance of account +stake_amount=$(jq -r --arg address "$account_address" --arg denom "$DENOM" '.app_state.bank.balances[] | select(.address == $address) | .coins[] | select(.denom == $denom) | .amount' $genesis_file_path) + +# Create gentx with staked amount equal to allocated balance +laconicd genesis gentx $KEY_NAME $stake_amount$DENOM --chain-id $CHAIN_ID --keyring-backend $KEYRING + +# Collect the gentx and validate +laconicd genesis collect-gentxs +laconicd genesis validate + +# Update the input genesis file +cp $genesis_file_path $input_genesis_file diff --git a/stack-orchestrator/config/mainnet-laconicd/run-laconicd.sh b/stack-orchestrator/config/mainnet-laconicd/run-laconicd.sh new file mode 100755 index 0000000..047b659 --- /dev/null +++ b/stack-orchestrator/config/mainnet-laconicd/run-laconicd.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +if [[ -n "$CERC_SCRIPT_DEBUG" ]]; then + set -x +fi + +set -e + +NODE_HOME=/root/.laconicd + +input_genesis_file=$NODE_HOME/tmp/genesis.json +if [ ! -f ${input_genesis_file} ]; then + echo "Genesis file not provided, exiting..." + exit 1 +fi + +echo "Env:" +echo "Moniker: $CERC_MONIKER" +echo "Chain Id: $CERC_CHAIN_ID" +echo "Persistent peers: $CERC_PEERS" +echo "Min gas price: $MIN_GAS_PRICE" +echo "Log level: $CERC_LOGLEVEL" + +# Set chain id in config +laconicd config set client chain-id $CERC_CHAIN_ID --home $NODE_HOME + +# Check if node data dir already exists +if [ -z "$(ls -A "$NODE_HOME/data")" ]; then + # Init node + echo "Initializing a new laconicd node with moniker $CERC_MONIKER and chain id $CERC_CHAIN_ID" + laconicd init $CERC_MONIKER --chain-id=$CERC_CHAIN_ID --home $NODE_HOME +else + echo "Node data dir $NODE_HOME/data already exists, skipping initialization..." +fi + +# Use provided config files +cp $input_genesis_file $NODE_HOME/config/genesis.json + +# Enable cors +sed -i 's/cors_allowed_origins.*$/cors_allowed_origins = ["*"]/' $NODE_HOME/config/config.toml + +# Update config with persistent peers +sed -i "s/^persistent_peers *=.*/persistent_peers = \"$CERC_PEERS\"/g" $NODE_HOME/config/config.toml + +# Enable telemetry (prometheus metrics: http://localhost:1317/metrics?format=prometheus) +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' 's/enabled = false/enabled = true/g' $NODE_HOME/config/app.toml + sed -i '' 's/prometheus-retention-time = 0/prometheus-retention-time = 60/g' $NODE_HOME/config/app.toml + sed -i '' 's/prometheus = false/prometheus = true/g' $NODE_HOME/config/config.toml +else + sed -i 's/enabled = false/enabled = true/g' $NODE_HOME/config/app.toml + sed -i 's/prometheus-retention-time = 0/prometheus-retention-time = 60/g' $NODE_HOME/config/app.toml + sed -i 's/prometheus = false/prometheus = true/g' $NODE_HOME/config/config.toml +fi + + +echo "Starting laconicd node..." +laconicd start \ + --api.enable \ + --minimum-gas-prices=${MIN_GAS_PRICE}alnt \ + --rpc.laddr="tcp://0.0.0.0:26657" \ + --gql-playground --gql-server \ + --log_level $CERC_LOGLEVEL \ + --home $NODE_HOME diff --git a/stack-orchestrator/container-build/cerc-laconicd/build.sh b/stack-orchestrator/container-build/cerc-laconicd/build.sh new file mode 100755 index 0000000..65bab74 --- /dev/null +++ b/stack-orchestrator/container-build/cerc-laconicd/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# Build cerc/laconicd +source ${CERC_CONTAINER_BASE_DIR}/build-base.sh +docker build -t cerc/laconicd:local ${build_command_args} ${CERC_REPO_BASE_DIR}/laconicd diff --git a/stack-orchestrator/stacks/mainnet-laconicd/README.md b/stack-orchestrator/stacks/mainnet-laconicd/README.md new file mode 100644 index 0000000..3ab806d --- /dev/null +++ b/stack-orchestrator/stacks/mainnet-laconicd/README.md @@ -0,0 +1 @@ +# mainnet-laconicd diff --git a/stack-orchestrator/stacks/mainnet-laconicd/stack.yml b/stack-orchestrator/stacks/mainnet-laconicd/stack.yml new file mode 100644 index 0000000..acfb443 --- /dev/null +++ b/stack-orchestrator/stacks/mainnet-laconicd/stack.yml @@ -0,0 +1,9 @@ +version: "1.0" +name: mainnet-laconicd +description: "Laconicd full node" +repos: + - git.vdb.to/cerc-io/laconicd@mainnet # TODO: Use a release +containers: + - cerc/laconicd +pods: + - mainnet-laconicd