diff --git a/.github/workflows/redeploy-to-dedicated-host.yaml b/.github/workflows/redeploy-to-dedicated-host.yaml new file mode 100644 index 00000000..62cbd0a7 --- /dev/null +++ b/.github/workflows/redeploy-to-dedicated-host.yaml @@ -0,0 +1,34 @@ +name: Re-deploy to calibration net on a dedicated server + +on: + workflow_dispatch: + +concurrency: + # Only allow one run at a time for this workflow + group: redeploy-to-dedicated-host + cancel-in-progress: true + +jobs: + deploy_to_dedicated_host: + runs-on: ubuntu-latest + steps: + - name: Checkout code repo + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + - name: Copy deployment script files to the host + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.DEDICATED_SERVER_IP_ADDRESS }} + username: textile + key: ${{ secrets.DEDICATED_SERVER_PRIVATE_KEY }} + source: "scripts/deploy_subnet_under_calibration_net/redeploy.sh" + target: "/home/textile" + - name: Run deploy scripts on the host + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.DEDICATED_SERVER_IP_ADDRESS }} + username: textile + key: ${{ secrets.DEDICATED_SERVER_PRIVATE_KEY }} + script: bash -il /home/textile/scripts/deploy_subnet_under_calibration_net/redeploy.sh ${{ github.ref_name }} + command_timeout: 120m diff --git a/infra/fendermint/scripts/ipfs.toml b/infra/fendermint/scripts/ipfs.toml index 6ee9f578..cec3784c 100644 --- a/infra/fendermint/scripts/ipfs.toml +++ b/infra/fendermint/scripts/ipfs.toml @@ -20,6 +20,10 @@ dependencies = ["docker-network-create"] extend = "ipfs-run" env = { "FLAGS" = "-d" } +[tasks.ipfs-stop] +env = { "CONTAINER_NAME" = "${IPFS_CONTAINER_NAME}" } +run_task = "docker-stop" + [tasks.ipfs-destroy] env = { "CONTAINER_NAME" = "${IPFS_CONTAINER_NAME}" } run_task = "docker-destroy" diff --git a/infra/fendermint/scripts/proxy.toml b/infra/fendermint/scripts/proxy.toml index 48643ec1..927b99ef 100644 --- a/infra/fendermint/scripts/proxy.toml +++ b/infra/fendermint/scripts/proxy.toml @@ -27,6 +27,10 @@ dependencies = ["docker-network-create"] extend = "proxy-run" env = { "CMD" = "proxy run", "FLAGS" = "-d" } +[tasks.proxy-stop] +env = { "CONTAINER_NAME" = "${PROXY_CONTAINER_NAME}" } +run_task = "docker-stop" + [tasks.proxy-destroy] env = { "CONTAINER_NAME" = "${PROXY_CONTAINER_NAME}" } run_task = "docker-destroy" diff --git a/infra/fendermint/scripts/subnet.toml b/infra/fendermint/scripts/subnet.toml index a82ce1dd..669b32b7 100644 --- a/infra/fendermint/scripts/subnet.toml +++ b/infra/fendermint/scripts/subnet.toml @@ -93,11 +93,11 @@ dependencies = [ [tasks.child-validator-restart] run_task = { name = [ - "cometbft-stop", - "fendermint-stop", - "ethapi-stop", - "proxy-stop", - "ipfs-stop", + "cometbft-destroy", + "fendermint-destroy", + "ethapi-destroy", + "proxy-destroy", + "ipfs-destroy", "fendermint-start-validator", "cometbft-start", "cometbft-wait", @@ -106,6 +106,21 @@ run_task = { name = [ "ipfs-start", ] } +[tasks.child-validator-restart-no-parent] +run_task = { name = [ + "cometbft-destroy", + "fendermint-destroy", + "ethapi-destroy", + "proxy-destroy", + "ipfs-destroy", + "fendermint-start-validator-no-parent", + "cometbft-start", + "cometbft-wait", + "ethapi-start", + "proxy-start", + "ipfs-start", +] } + [tasks.child-fullnode-down] run_task = "testnode-down" diff --git a/scripts/deploy_subnet_no_parent/restart.sh b/scripts/deploy_subnet_no_parent/restart.sh new file mode 100644 index 00000000..6694446f --- /dev/null +++ b/scripts/deploy_subnet_no_parent/restart.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +set -euo pipefail + +DASHES='------' +if [[ ! -v IPC_FOLDER ]]; then + IPC_FOLDER=${HOME}/ipc +else + IPC_FOLDER=${IPC_FOLDER} +fi +IPC_CONFIG_FOLDER=${HOME}/.ipc + +CMT_P2P_HOST_PORTS=(26656 26756 26856) +CMT_RPC_HOST_PORTS=(26657 26757 26857) +ETHAPI_HOST_PORTS=(8545 8645 8745) +RESOLVER_HOST_PORTS=(26655 26755 26855) +PROXY_HOST_PORTS=(8001 8002 8003) +IPFS_SWARM_HOST_PORTS=(4001 4002 4003) +IPFS_RPC_HOST_PORTS=(5001 5002 5003) +IPFS_GATEWAY_HOST_PORTS=(8080 8081 8082) + +# Use "dummy" subnet +subnet_id="/r314159/t410f726d2jv6uj4mpkcbgg5ndlpp3l7dd5rlcpgzkoi" +echo "Use existing subnet id: $subnet_id" +subnet_folder=$IPC_CONFIG_FOLDER/$(echo $subnet_id | sed 's|^/||;s|/|-|g') + +# Step 1: Restart validators +# Step 1.1: Rebuild fendermint docker +echo "$DASHES Rebuild fendermint docker" +cd ${IPC_FOLDER}/fendermint +make clean +make docker-build + +# Step 1.2: Start other validator node +echo "$DASHES Restart validator nodes" +cd ${IPC_FOLDER} +for i in {0..2} +do + cargo make --makefile infra/fendermint/Makefile.toml \ + -e NODE_NAME=validator-${i} \ + -e PRIVATE_KEY_PATH=${IPC_CONFIG_FOLDER}/validator_${i}.sk \ + -e SUBNET_ID=${subnet_id} \ + -e CMT_P2P_HOST_PORT=${CMT_P2P_HOST_PORTS[i]} \ + -e CMT_RPC_HOST_PORT=${CMT_RPC_HOST_PORTS[i]} \ + -e ETHAPI_HOST_PORT=${ETHAPI_HOST_PORTS[i]} \ + -e RESOLVER_HOST_PORT=${RESOLVER_HOST_PORTS[i]} \ + -e PROXY_HOST_PORT=${PROXY_HOST_PORTS[i]} \ + -e IPFS_SWARM_HOST_PORT=${IPFS_SWARM_HOST_PORTS[i]} \ + -e IPFS_RPC_HOST_PORT=${IPFS_RPC_HOST_PORTS[i]} \ + -e IPFS_GATEWAY_HOST_PORT=${IPFS_GATEWAY_HOST_PORTS[i]} \ + -e IPFS_PROFILE="local-discovery" \ + -e FM_PULL_SKIP=1 \ + -e FM_LOG_LEVEL="info" \ + child-validator-restart-no-parent +done + +# Step 2: Test +# Step 2.1: Test ETH API endpoint +echo "$DASHES Test ETH API endpoints of validator nodes" +for i in {0..2} +do + curl --location http://localhost:${ETHAPI_HOST_PORTS[i]} \ + --header 'Content-Type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "method":"eth_blockNumber", + "params":[], + "id":83 + }' +done + +# Step 2.2: Test proxy endpoint +printf "\n$DASHES Test proxy endpoints of validator nodes\n" +for i in {0..2} +do + curl --location http://localhost:${PROXY_HOST_PORTS[i]}/health +done + +# Step 3: Print a summary of the deployment +cat << EOF +############################ +# # +# IPC deployment ready! 🚀 # +# # +############################ +Subnet ID: +$subnet_id + +Proxy API: +http://localhost:${PROXY_HOST_PORTS[0]} +http://localhost:${PROXY_HOST_PORTS[1]} +http://localhost:${PROXY_HOST_PORTS[2]} + +IPFS API: +http://localhost:${IPFS_RPC_HOST_PORTS[0]} +http://localhost:${IPFS_RPC_HOST_PORTS[1]} +http://localhost:${IPFS_RPC_HOST_PORTS[2]} + +ETH API: +http://localhost:${ETHAPI_HOST_PORTS[0]} +http://localhost:${ETHAPI_HOST_PORTS[1]} +http://localhost:${ETHAPI_HOST_PORTS[2]} + +Accounts: +$(jq -r '.app_state.accounts[] | "\(.meta.Account.owner): \(.balance) coin units"' ${subnet_folder}/validator-0/genesis.json) + +Private keys (hex ready to import in MetaMask): +$(cat ${IPC_CONFIG_FOLDER}/validator_0.sk | base64 -d | xxd -p -c 1000000) +$(cat ${IPC_CONFIG_FOLDER}/validator_1.sk | base64 -d | xxd -p -c 1000000) +$(cat ${IPC_CONFIG_FOLDER}/validator_2.sk | base64 -d | xxd -p -c 1000000) + +Chain ID: +$(curl -s --location --request POST 'http://localhost:8645/' --header 'Content-Type: application/json' --data-raw '{ "jsonrpc":"2.0", "method":"eth_chainId", "params":[], "id":1 }' | jq -r '.result' | xargs printf "%d") + +Fendermint API: +http://localhost:26658 + +CometBFT API: +http://localhost:${CMT_RPC_HOST_PORTS[0]} +EOF diff --git a/scripts/deploy_subnet_no_parent/start.sh b/scripts/deploy_subnet_no_parent/start.sh index c32a5898..bce286ac 100644 --- a/scripts/deploy_subnet_no_parent/start.sh +++ b/scripts/deploy_subnet_no_parent/start.sh @@ -58,7 +58,7 @@ cargo install cargo-make echo "$DASHES Installing toml-cli" cargo install toml-cli -# Step 1.3: Install Foundry +# Step 1.4: Install Foundry echo "$DASHES Check foundry..." if which foundryup ; then echo "$DASHES foundry is already installed." @@ -68,7 +68,7 @@ else foundryup fi -# Step 1.3.1: Install node +# Step 1.5 Install node echo "$DASHES Check node..." if which node ; then echo "$DASHES node is already installed." @@ -79,7 +79,7 @@ else nvm install --default lts/* fi -# Step 1.4: Install docker +# Step 1.6: Install docker echo "$DASHES check docker" if which docker ; then echo "$DASHES docker is already installed." @@ -160,11 +160,11 @@ done # Step 4.2: Setup validators # Use "dummy" subnet subnet_id="/r314159/t410f726d2jv6uj4mpkcbgg5ndlpp3l7dd5rlcpgzkoi" -# Remove leading '/' and change middle '/' into '-' +echo "Use existing subnet id: $subnet_id" subnet_folder=$IPC_CONFIG_FOLDER/$(echo $subnet_id | sed 's|^/||;s|/|-|g') rm -rf subnet_folder -# Init validators +# Step 5: Init validators echo "$DASHES Init validators" cd ${IPC_FOLDER} for i in {0..2} @@ -176,7 +176,7 @@ do child-validator-no-parent-init done -# Tableland: Setup proxy wallets +# Step 6: Setup proxy wallets echo "$DASHES Configuring proxy wallets" for i in {0..2} do @@ -205,13 +205,13 @@ do cp ${IPC_CONFIG_FOLDER}/genesis.json ${subnet_folder}/validator-${i} done -# Step 5: Start validators -# Step 5.1 (optional): Rebuild fendermint docker +# Step 7: Start validators +# Step 7.1 (optional): Rebuild fendermint docker cd ${IPC_FOLDER}/fendermint make clean make docker-build -# Step 5.2: Start the bootstrap validator node +# Step 7.2: Start the bootstrap validator node echo "$DASHES Start the first validator node as bootstrap" cd ${IPC_FOLDER} bootstrap_output=$(cargo make --makefile infra/fendermint/Makefile.toml \ @@ -240,7 +240,7 @@ echo "Bootstrap node endpoint: ${bootstrap_node_endpoint}" bootstrap_resolver_endpoint="/dns/validator-0-fendermint/tcp/${RESOLVER_HOST_PORTS[0]}/p2p/${bootstrap_peer_id}" echo "Bootstrap resolver endpoint: ${bootstrap_resolver_endpoint}" -# Step 5.3: Start other validator node +# Step 7.3: Start other validator node echo "$DASHES Start the other validator nodes" cd ${IPC_FOLDER} for i in {1..2} @@ -265,7 +265,8 @@ do child-validator-no-parent done -# Step 6a: Test ETH API endpoint +# Step 8: Test +# Step 8.1: Test ETH API endpoint echo "$DASHES Test ETH API endpoints of validator nodes" for i in {0..2} do @@ -279,14 +280,14 @@ do }' done -# Step 6b: Test proxy endpoint -echo "$DASHES Test proxy endpoints of validator nodes" +# Step 8.2: Test proxy endpoint +printf "\n$DASHES Test proxy endpoints of validator nodes\n" for i in {0..2} do curl --location http://localhost:${PROXY_HOST_PORTS[i]}/health done -# Step 7: Print a summary of the deployment +# Step 9: Print a summary of the deployment cat << EOF ############################ # # diff --git a/scripts/deploy_subnet_no_parent/stop.sh b/scripts/deploy_subnet_no_parent/stop.sh index ec95b903..7c99e06e 100644 --- a/scripts/deploy_subnet_no_parent/stop.sh +++ b/scripts/deploy_subnet_no_parent/stop.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -euxo pipefail +set -euo pipefail if [[ ! -v IPC_FOLDER ]]; then IPC_FOLDER=${HOME}/ipc @@ -10,6 +10,7 @@ fi # Use "dummy" subnet subnet_id="/r314159/t410f726d2jv6uj4mpkcbgg5ndlpp3l7dd5rlcpgzkoi" +echo "Use existing subnet id: $subnet_id" # Stop validators cd ${IPC_FOLDER} diff --git a/scripts/deploy_subnet_under_calibration_net/deploy.sh b/scripts/deploy_subnet_under_calibration_net/deploy.sh index 8e3b5f9a..782513cb 100644 --- a/scripts/deploy_subnet_under_calibration_net/deploy.sh +++ b/scripts/deploy_subnet_under_calibration_net/deploy.sh @@ -71,7 +71,7 @@ cargo install cargo-make echo "$DASHES Installing toml-cli" cargo install toml-cli -# Step 1.3: Install Foundry +# Step 1.4: Install Foundry echo "$DASHES Check foundry..." if which foundryup ; then echo "$DASHES foundry is already installed." @@ -81,7 +81,7 @@ else foundryup fi -# Step 1.3.1: Install node +# Step 1.5: Install node echo "$DASHES Check node..." if which node ; then echo "$DASHES node is already installed." @@ -92,7 +92,7 @@ else nvm install --default lts/* fi -# Step 1.4: Install docker +# Step 1.6: Install docker echo "$DASHES check docker" if which docker ; then echo "$DASHES docker is already installed." @@ -192,8 +192,8 @@ echo "$DASHES Creating a child subnet..." create_subnet_output=$(ipc-cli subnet create --parent /r314159 --min-validators 3 --min-validator-stake 1 --bottomup-check-period 30 --from $default_wallet_address --permission-mode collateral --supply-source-kind native 2>&1) echo $create_subnet_output subnet_id=$(echo $create_subnet_output | sed 's/.*with id: \([^ ]*\).*/\1/') - echo "Created new subnet id: $subnet_id" +subnet_folder=$IPC_CONFIG_FOLDER/$(echo $subnet_id | sed 's|^/||;s|/|-|g') # Step 5 (alternative): Use an already-created subnet #subnet_id=/r314159/t410flp4jf7keqcf5bqogrkx4wpkygiskykcvpaigicq @@ -221,7 +221,6 @@ do done # Step 8: Start validators - # Step 8.1 (optional): Rebuild fendermint docker cd ${IPC_FOLDER}/fendermint make clean @@ -290,10 +289,7 @@ do child-validator done -# Remove leading '/' and change middle '/' into '-' -subnet_folder=$IPC_CONFIG_FOLDER/$(echo $subnet_id | sed 's|^/||;s|/|-|g') - -# Tableland: Fund proxy wallet in the subnet +# Step 8.4: Fund proxy wallet in the subnet echo "$DASHES Fund proxy wallets in the subnet" for i in {0..2} do @@ -307,7 +303,8 @@ do ipc-cli wallet remove --wallet-type evm --address ${proxy_address} done -# Step 9a: Test ETH API endpoint +# Step 9: Test +# Step 9.1: Test ETH API endpoint echo "$DASHES Test ETH API endpoints of validator nodes" for i in {0..2} do @@ -321,8 +318,8 @@ do }' done -# Step 9b: Test proxy endpoint -echo "$DASHES Test proxy endpoints of validator nodes" +# Step 9.2: Test proxy endpoint +printf "\n$DASHES Test proxy endpoints of validator nodes\n" for i in {0..2} do curl --location http://localhost:${PROXY_HOST_PORTS[i]}/health diff --git a/scripts/deploy_subnet_under_calibration_net/redeploy.sh b/scripts/deploy_subnet_under_calibration_net/redeploy.sh new file mode 100644 index 00000000..cc405694 --- /dev/null +++ b/scripts/deploy_subnet_under_calibration_net/redeploy.sh @@ -0,0 +1,231 @@ +#!/bin/bash + +# IPC Quick Start Script +# See also https://github.com/consensus-shipyard/ipc/blob/main/docs/ipc/quickstart-calibration.md + +# Known issues: +# 1. Need to previously manual enable sudo without password on the host +# 2. You may need to rerun the script after docker installation for the first time +# 2. You may need to manually install nodejs and npm on the host + +set -euo pipefail + +eval `ssh-agent -s` +ssh-add +ssh-add ${HOME}/.ssh/id_rsa.ipc + +DASHES='------' +if [[ ! -v IPC_FOLDER ]]; then + IPC_FOLDER=${HOME}/ipc +else + IPC_FOLDER=${IPC_FOLDER} +fi +IPC_CONFIG_FOLDER=${HOME}/.ipc + +CMT_P2P_HOST_PORTS=(26656 26756 26856) +CMT_RPC_HOST_PORTS=(26657 26757 26857) +ETHAPI_HOST_PORTS=(8545 8645 8745) +RESOLVER_HOST_PORTS=(26655 26755 26855) +PROXY_HOST_PORTS=(8001 8002 8003) +IPFS_SWARM_HOST_PORTS=(4001 4002 4003) +IPFS_RPC_HOST_PORTS=(5001 5002 5003) +IPFS_GATEWAY_HOST_PORTS=(8080 8081 8082) + +if (($# != 1)); then + echo "Arguments: /dev/null + sudo apt-get update + sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + # Remove the need to use sudo + getent group docker || sudo groupadd docker + sudo usermod -aG docker $USER + newgrp docker + + # Test running docker without sudo + docker ps +fi + +# Make sure we re-read the latest env before finishing dependency installation. +set +u +source ${HOME}/.bashrc +set -u + +# Step 2: Prepare code repo and build ipc-cli +if ! $local_deploy ; then + echo "$DASHES Preparing ipc repo..." + if ! ls $IPC_FOLDER ; then + git clone --recurse-submodules -j8 git@github.com-ipc:amazingdatamachine/ipc.git ${IPC_FOLDER} + fi + cd ${IPC_FOLDER} + git fetch + git stash + git checkout $head_ref + git pull --rebase origin $head_ref + git submodule sync + git submodule update --init --recursive +fi + +# Step 3: Use already-created subnet +subnet_id=$(toml get -r ${IPC_CONFIG_FOLDER}/config.toml subnets[1].id) +echo "Use existing subnet id: $subnet_id" +subnet_folder=$IPC_CONFIG_FOLDER/$(echo $subnet_id | sed 's|^/||;s|/|-|g') +parent_gateway_address=$(toml get -r ${IPC_CONFIG_FOLDER}/config.toml subnets[0].config.gateway_addr) +parent_registry_address=$(toml get -r ${IPC_CONFIG_FOLDER}/config.toml subnets[0].config.registry_addr) + +# Step 4: Restart validators +# Step 4.1: Rebuild fendermint docker +echo "$DASHES Rebuild fendermint docker" +cd ${IPC_FOLDER}/fendermint +make clean +make docker-build + +# Step 4.2: Start other validator node +echo "$DASHES Restart validator nodes" +cd ${IPC_FOLDER} +for i in {0..2} +do + cargo make --makefile infra/fendermint/Makefile.toml \ + -e NODE_NAME=validator-${i} \ + -e PRIVATE_KEY_PATH=${IPC_CONFIG_FOLDER}/validator_${i}.sk \ + -e SUBNET_ID=${subnet_id} \ + -e CMT_P2P_HOST_PORT=${CMT_P2P_HOST_PORTS[i]} \ + -e CMT_RPC_HOST_PORT=${CMT_RPC_HOST_PORTS[i]} \ + -e ETHAPI_HOST_PORT=${ETHAPI_HOST_PORTS[i]} \ + -e RESOLVER_HOST_PORT=${RESOLVER_HOST_PORTS[i]} \ + -e PROXY_HOST_PORT=${PROXY_HOST_PORTS[i]} \ + -e IPFS_SWARM_HOST_PORT=${IPFS_SWARM_HOST_PORTS[i]} \ + -e IPFS_RPC_HOST_PORT=${IPFS_RPC_HOST_PORTS[i]} \ + -e IPFS_GATEWAY_HOST_PORT=${IPFS_GATEWAY_HOST_PORTS[i]} \ + -e IPFS_PROFILE="local-discovery" \ + -e PARENT_REGISTRY=${parent_registry_address} \ + -e PARENT_GATEWAY=${parent_gateway_address} \ + -e FM_PULL_SKIP=1 \ + -e FM_LOG_LEVEL="info" \ + child-validator-restart +done + +# Step 5: Test +# Step 5.1: Test ETH API endpoint +echo "$DASHES Test ETH API endpoints of validator nodes" +for i in {0..2} +do + curl --location http://localhost:${ETHAPI_HOST_PORTS[i]} \ + --header 'Content-Type: application/json' \ + --data '{ + "jsonrpc":"2.0", + "method":"eth_blockNumber", + "params":[], + "id":83 + }' +done + +# Step 5.2: Test proxy endpoint +printf "\n$DASHES Test proxy endpoints of validator nodes\n" +for i in {0..2} +do + curl --location http://localhost:${PROXY_HOST_PORTS[i]}/health +done + +# Step 6: Start a relayer process +# Kill existing relayer if there's one +pkill -f "relayer" || true +# Start relayer +echo "$DASHES Start relayer process (in the background)" +nohup ipc-cli checkpoint relayer --subnet $subnet_id --submitter 0xA08aE9E8c038CAf9765D7Db725CA63a92FCf12Ce > nohup.out 2> nohup.err < /dev/null & + +# Step 7: Print a summary of the deployment +cat << EOF +############################ +# # +# IPC deployment ready! 🚀 # +# # +############################ +Subnet ID: +$subnet_id + +Proxy API: +http://localhost:${PROXY_HOST_PORTS[0]} +http://localhost:${PROXY_HOST_PORTS[1]} +http://localhost:${PROXY_HOST_PORTS[2]} + +IPFS API: +http://localhost:${IPFS_RPC_HOST_PORTS[0]} +http://localhost:${IPFS_RPC_HOST_PORTS[1]} +http://localhost:${IPFS_RPC_HOST_PORTS[2]} + +ETH API: +http://localhost:${ETHAPI_HOST_PORTS[0]} +http://localhost:${ETHAPI_HOST_PORTS[1]} +http://localhost:${ETHAPI_HOST_PORTS[2]} + +Accounts: +$(jq -r '.accounts[] | "\(.meta.Account.owner): \(.balance) coin units"' ${subnet_folder}/validator-0/genesis.json) + +Private keys (hex ready to import in MetaMask): +$(cat ${IPC_CONFIG_FOLDER}/validator_0.sk | base64 -d | xxd -p -c 1000000) +$(cat ${IPC_CONFIG_FOLDER}/validator_1.sk | base64 -d | xxd -p -c 1000000) +$(cat ${IPC_CONFIG_FOLDER}/validator_2.sk | base64 -d | xxd -p -c 1000000) + +Chain ID: +$(curl -s --location --request POST 'http://localhost:8645/' --header 'Content-Type: application/json' --data-raw '{ "jsonrpc":"2.0", "method":"eth_chainId", "params":[], "id":1 }' | jq -r '.result' | xargs printf "%d") + +Fendermint API: +http://localhost:26658 + +CometBFT API: +http://localhost:${CMT_RPC_HOST_PORTS[0]} +EOF