diff --git a/cluster/scripts/charon-add-validator/main.sh b/cluster/scripts/charon-add-validator/main.sh new file mode 100644 index 0000000..eedccef --- /dev/null +++ b/cluster/scripts/charon-add-validator/main.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# VARS +SERVICE_OK=0 +ATTEMPTS=0 +MAX_ATTEMPTS=10 +INFO="[ INFO | container-add-validator:]" + +# Function that runs the validator addition logic +run_validator_logic() { + echo "${INFO} Add validators for $CHARON_SERVICE_NAME" + + # Check if ADD_VALIDATOR is setting in config for current running service + if [[ "$ADD_VALIDATOR_TARGET_CLUSTER" =~ (^|,)($CHARON_SERVICE_NAME)(,|$) ]]; then + echo "${INFO} Start running charon add-validators command" + + charon alpha add-validators \ + --data-dir="$CHARON_ROOT_DIR" \ + --num-validators "$ADD_VALIDATOR_NUM_VALIDATORS" \ + --withdrawal-addresses="$ADD_VALIDATOR_WITHDRAWAL_ADDRESS" \ + --fee-recipient-addresses="$ADD_VALIDATOR_FEE_RECEPIENT_ADDRESS" \ + --p2p-relays https://4.relay.obol.dev \ + --output-dir=/tmp/.charon + + if [[ $? -ne 0 ]]; then + echo "${INFO} charon add-validators failed. Exiting..." + rm -f /import/add_validator + rm -rf /tmp/.charon + exit 1 + fi + + echo "${INFO} Stopping charon and lodestar during upgrade processes..." + supervisorctl stop charon lodestar + + echo "${INFO} Upgrade .charon directory with backing up previous .charon to /tmp/.charon" + cp -r "$CHARON_ROOT_DIR" /tmp/.charon.bck && mv /tmp/.charon "$CHARON_ROOT_DIR" + + echo "${INFO} Starting charon and lodestar processes..." + supervisorctl start charon lodestar + + while [[ "$ATTEMPTS" -lt "$MAX_ATTEMPTS" ]]; do + if supervisorctl status charon | grep -q "RUNNING"; then + SERVICE_OK=1 + break + fi + + echo "${INFO} charon not ready, waiting 2 seconds... (Attempt ${ATTEMPTS}/${MAX_ATTEMPTS})" + sleep 3 + ATTEMPTS=$((ATTEMPTS + 1)) + done + + if [[ "$SERVICE_OK" -eq 1 ]]; then + echo "${INFO} Validator(s) added, charon is running." + touch "$CHARON_ROOT_DIR/.charon_added_validator_state" + else + echo "${INFO} Validator(s) was not added, restoring .charon state folder to previous one and restart cluster. Check logs for more details" + mv /tmp/.charon.bck "$CHARON_ROOT_DIR" + supervisorctl restart charon lodestar + fi + fi +} + +# Watch for the creation of /import/add_validator and trigger logic +inotifywait -m /import -e create | +while read path action file; do + if [ "$file" == "add_validator" ]; then + echo "${INFO} Trigger file detected, executing validator logic..." + run_validator_logic + rm -f /import/add_validator + fi +done diff --git a/cluster/scripts/charon/run-charon.sh b/cluster/scripts/charon/run-charon.sh index 78a1fe8..10e1b74 100755 --- a/cluster/scripts/charon/run-charon.sh +++ b/cluster/scripts/charon/run-charon.sh @@ -12,6 +12,7 @@ ENR_FILE=${CHARON_ROOT_DIR}/enr DEFINITION_FILE_URL_FILE=${CHARON_ROOT_DIR}/definition_file_url.txt CHARON_LOCK_FILE=${CHARON_ROOT_DIR}/cluster-lock.json +CHARON_ADDED_VALIDATOR_STATE_FILE=${CHARON_ROOT_DIR}/.charon_added_validator_state if [ -n "$DEFINITION_FILE_URL" ]; then echo "$DEFINITION_FILE_URL" >$DEFINITION_FILE_URL_FILE @@ -36,7 +37,7 @@ function get_beacon_node_endpoint() { if [ -n "$CUSTOM_BEACON_NODE_URLS" ]; then if [ -n "$local_beacon_api" ]; then - CHARON_BEACON_NODE_ENDPOINTS="$CHARON_BEACON_NODE_ENDPOINTS,$local_beacon_api" + CHARON_BEACON_NODE_ENDPOINTS="$CUSTOM_BEACON_NODE_URLS,$local_beacon_api" else CHARON_BEACON_NODE_ENDPOINTS=$CUSTOM_BEACON_NODE_URLS fi @@ -106,11 +107,16 @@ function check_DKG() { } function run_charon() { - if [ "$ENABLE_MEV_BOOST" = true ]; then - CHARON_EXTRA_OPTS="--builder-api $CHARON_EXTRA_OPTS" + if [ "$ENABLE_MEV_BOOST" = true ] && [ -f "$CHARON_ADDED_VALIDATOR_STATE_FILE" ]; then + CHARON_EXTRA_OPTS="--builder-api --no-verify ${CHARON_EXTRA_OPTS}" + + elif [ "$ENABLE_MEV_BOOST" = true ]; then + CHARON_EXTRA_OPTS="--builder-api ${CHARON_EXTRA_OPTS}" fi - exec charon run --private-key-file=$ENR_PRIVATE_KEY_FILE --lock-file=$CHARON_LOCK_FILE ${CHARON_EXTRA_OPTS} + if [ "$ENABLE_MEV_BOOST" = true ] || [ -f $CHARON_ADDED_VALIDATOR_STATE_FILE ]; then + exec charon run --private-key-file=$ENR_PRIVATE_KEY_FILE --lock-file=$CHARON_LOCK_FILE ${CHARON_EXTRA_OPTS} + fi } ######## diff --git a/cluster/supervisord.conf b/cluster/supervisord.conf index 9cb8a63..e5fd169 100644 --- a/cluster/supervisord.conf +++ b/cluster/supervisord.conf @@ -45,4 +45,14 @@ autorestart = true # Should not be required to start automatically stdout_logfile = /dev/stdout stdout_logfile_maxbytes = 0 stderr_logfile = /dev/stderr -stderr_logfile_maxbytes = 0 \ No newline at end of file +stderr_logfile_maxbytes = 0 + +[program:addvalidator] +command = /usr/local/bin/scripts/charon-add-validator/main.sh +priority = 3 +autostart = true +autorestart = true +stdout_logfile = /dev/stdout +stdout_logfile_maxbytes = 0 +stderr_logfile = /dev/stderr +stderr_logfile_maxbytes = 0 diff --git a/docker-compose.yml b/docker-compose.yml index 9a93c61..7313714 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,7 @@ services: CHARON_LOG_LEVEL: info CHARON_LOKI_ADDRESSES: http://loki.dms.dappnode:3100/loki/api/v1/push CHARON_P2P_RELAYS: https://0.relay.obol.tech,https://1.relay.obol.tech/ + CHARON_SERVICE_NAME: cluster-1 CHARON_LOKI_SERVICE: cluster-1 ENABLE_MEV_BOOST: "false" CHARON_EXTRA_OPTS: "" @@ -51,6 +52,7 @@ services: CHARON_P2P_RELAYS: https://0.relay.obol.tech,https://1.relay.obol.tech/ CHARON_P2P_TCP_ADDRESS: "" CHARON_P2P_UDP_ADDRESS: "" + CHARON_SERVICE_NAME: cluster-2 CHARON_LOKI_SERVICE: cluster-2 ENABLE_MEV_BOOST: "false" CHARON_EXTRA_OPTS: "" @@ -83,6 +85,7 @@ services: CHARON_LOG_LEVEL: info CHARON_LOKI_ADDRESSES: http://loki.dms.dappnode:3100/loki/api/v1/push CHARON_P2P_RELAYS: https://0.relay.obol.tech,https://1.relay.obol.tech/ + CHARON_SERVICE_NAME: cluster-3 CHARON_LOKI_SERVICE: cluster-3 ENABLE_MEV_BOOST: "false" CHARON_EXTRA_OPTS: "" @@ -115,6 +118,7 @@ services: CHARON_LOG_LEVEL: info CHARON_LOKI_ADDRESSES: http://loki.dms.dappnode:3100/loki/api/v1/push CHARON_P2P_RELAYS: https://0.relay.obol.tech,https://1.relay.obol.tech/ + CHARON_SERVICE_NAME: cluster-4 CHARON_LOKI_SERVICE: cluster-4 ENABLE_MEV_BOOST: "false" CHARON_EXTRA_OPTS: "" @@ -147,6 +151,7 @@ services: CHARON_LOG_LEVEL: info CHARON_LOKI_ADDRESSES: http://loki.dms.dappnode:3100/loki/api/v1/push CHARON_P2P_RELAYS: https://0.relay.obol.tech,https://1.relay.obol.tech/ + CHARON_SERVICE_NAME: cluster-5 CHARON_LOKI_SERVICE: cluster-5 ENABLE_MEV_BOOST: "false" CHARON_EXTRA_OPTS: "" diff --git a/setup-wizard.yml b/setup-wizard.yml index befdc85..1cfcb2b 100644 --- a/setup-wizard.yml +++ b/setup-wizard.yml @@ -8,7 +8,7 @@ fields: **READ BEFORE PROCEEDING** This package contains 5 instances of Obol, so you can run up to 5 clusters with different people. Each cluster will have its own configuration. You can have a Cluster being part of Lido's SDVTM and another one with your friends and up to 5 different clusters. Each cluster can handle hundreds of validators. - + It is **highly recommended** that you backup the package every time you make a change to the configuration of any of the clusters! --- @@ -17,13 +17,16 @@ fields: Select 'New cluster / Simple update' if this is your initial setup or if you are updating an existing setup. - For importing configurations, choose 'URL' to specify definition file URLs or 'File' to upload a compressed file containing a node artifact. Select 'URL' for SDVTM. + For importing configurations, choose 'URL' to specify definition file URLs or 'File' to upload a compressed file containing a node artifact. Select 'URL' for SDVTM. + + To add new validators, fill in the required fields: *Number of validators*, *Target cluster*, *Withdrawal address*, and *Fee Recipient Addresses*. Afterward, use the DAppNode file manager to upload an empty file named `add_validator` (with no extension) to the /import target folder. Note file upload will not be available on config tab after install, but in the file manager tab. enum: - "New cluster / Simple update" - "URL" - "File" + - "Add Validators" target: type: environment name: CONFIG_MODE @@ -34,7 +37,7 @@ fields: title: Charons to monitor by Obol (optional) description: | Leave blank if you haven't been asked to do this. - + Comma separated list of charon services to monitor by Obol team for performance and reliability. The prometheus service will send the metrics to the server defined by the monitoring URL. Example: "1,2,3" @@ -190,3 +193,130 @@ fields: pattern: "(\\.tar.xz|\\.tar.gz|\\.zip)" required: false if: { "config_mode": { "enum": ["File"] } } + + - id: validator-switch-1 + target: + type: fileUpload + path: /import/add_validator + service: "cluster-1" + title: Add validator trigger file + description: | + Created empty file without extensions named: add_validator to trigger adding validator logic + pattern: "^add_validator$" + patternErrorMessage: | + "Can't upload provided file. Make sure you are uploading file without any additional extensions like .txt, upload add_validator file without any extenions and try again." + required: false + if: { "config_mode": { "enum": ["Add Validators"] } } + + - id: validator-switch-2 + target: + type: fileUpload + path: /import/add_validator + service: "cluster-2" + title: Add validator trigger file + description: | + Created empty file without extensions named: add_validator to trigger adding validator logic + pattern: "^add_validator$" + patternErrorMessage: | + "Can't upload provided file. Make sure you are uploading file without any additional extensions like .txt, upload add_validator file without any extenions and try again." + required: false + if: { "config_mode": { "enum": ["Add Validators"] } } + + - id: validator-switch-3 + target: + type: fileUpload + path: /import/add_validator + service: "cluster-3" + title: Add validator trigger file + description: | + Created empty file without extensions named: add_validator to trigger adding validator logic + pattern: "^add_validator$" + patternErrorMessage: | + "Can't upload provided file. Make sure you are uploading file without any additional extensions like .txt, upload add_validator file without any extenions and try again." + required: false + if: { "config_mode": { "enum": ["Add Validators"] } } + + - id: validator-switch-4 + target: + type: fileUpload + path: /import/add_validator + service: "cluster-4" + title: Add validator trigger file + description: | + Created empty file without extensions named: add_validator to trigger adding validator logic + pattern: "^add_validator$" + patternErrorMessage: | + "Can't upload provided file. Make sure you are uploading file without any additional extensions like .txt, upload add_validator file without any extenions and try again." + required: true + if: { "config_mode": { "enum": ["Add Validators"] } } + + - id: validator-switch-5 + target: + type: fileUpload + path: /import/add_validator + service: "cluster-5" + title: Add validator trigger file + description: | + Created empty file without extensions named: add_validator to trigger adding validator logic + pattern: "^add_validator$" + patternErrorMessage: | + "Can't upload provided file. Make sure you are uploading file without any additional extensions like .txt, upload add_validator file without any extenions and try again." + required: true + if: { "config_mode": { "enum": ["Add Validators"] } } + + - id: num_validators + target: + type: environment + name: ADD_VALIDATOR_NUM_VALIDATORS + service: ["cluster-1", "cluster-2", "cluster-3", "cluster-4", "cluster-5"] + title: Number of validators + description: | + Enter the number of validators to add (1-100). + type: string + required: true + pattern: "^(100|[1-9][0-9]?)$" + patternErrorMessage: "Please enter a number between 1 and 100." + if: { "config_mode": { "enum": ["Add Validators"] } } + + - id: target_cluster + target: + type: environment + name: ADD_VALIDATOR_TARGET_CLUSTER + service: ["cluster-1", "cluster-2", "cluster-3", "cluster-4", "cluster-5"] + title: Target clusters to process add-validators procedure + description: | + Set list of target clusters to add-validators separated by commas with np spaces. + Examples: `cluster-1` or `cluster-1,cluster-3` + default: "cluster-1" + required: true + pattern: "^cluster-\\d+(,cluster-\\d+)*$" + patternErrorMessage: Must be a valid set of clusters, for e.g. cluster-1 or cluster-1,cluster-2 *without spaces!* + if: { "config_mode": { "enum": ["Add Validators"] } } + + - id: withdrawal_address + target: + type: environment + name: ADD_VALIDATOR_WITHDRAWAL_ADDRESS + # Use the target_cluster value to construct the service name + service: ["cluster-1", "cluster-2", "cluster-3", "cluster-4", "cluster-5"] + title: Withdrawal address + description: | + Enter the withdrawal address (0x...). + required: true + pattern: "^0x[a-fA-F0-9]{40}$" + patternErrorMessage: Must be a valid Ethereum address (0x...) + if: { "config_mode": { "enum": ["Add Validators"] } } + + - id: fee_recipient_address + target: + type: environment + name: ADD_VALIDATOR_FEE_RECEPIENT_ADDRESS + # Use the target_cluster value to construct the service name + service: ["cluster-1", "cluster-2", "cluster-3", "cluster-4", "cluster-5"] + title: Fee Recipient Addresses + description: | + Enter the fee recipient address (0x...). + required: true + pattern: "^0x[a-fA-F0-9]{40}$" + patternErrorMessage: Must be a valid Ethereum address (0x...) + if: { "config_mode": { "enum": ["Add Validators"] } }