diff --git a/.eth/exit_messages/.empty b/.eth/exit_messages/.empty new file mode 100644 index 00000000..e69de29b diff --git a/.gitignore b/.gitignore index f8d8cd73..9305bf4c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ ext-network.yml.original .eth/* !.eth/README.md !.eth/validator_keys/.empty +!.eth/exit_messages/.empty !.eth/ethdo/README.md !.eth/ethdo/create-withdrawal-change.sh *.swp diff --git a/ethd b/ethd index 83119593..a9825ee6 100755 --- a/ethd +++ b/ethd @@ -104,8 +104,9 @@ upgrade_compose() { echo "This script cannot update compose on Ubuntu ${__major_version}. Consider upgrading to 22.04 or 20.04" exit 0 fi - ${__auto_sudo} apt-get remove -y docker-compose - echo "Removed docker-compose" + #Out of caution as this may trigger an autoremove of docker.io, tbd + #${__auto_sudo} apt-get remove -y docker-compose + #echo "Removed docker-compose" ${__auto_sudo} mkdir -p /etc/apt/keyrings ${__auto_sudo} curl -fsSL https://download.docker.com/linux/ubuntu/gpg | ${__auto_sudo} gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg ${__auto_sudo} echo \ @@ -124,8 +125,9 @@ upgrade_compose() { echo "This script cannot update compose on Debian ${__major_version}. Consider upgrading to 11 or 12." exit 0 fi - ${__auto_sudo} apt-get remove -y docker-compose - echo "Removed docker-compose" + #Out of caution as this may trigger an autoremove of docker.io, tbd + #${__auto_sudo} apt-get remove -y docker-compose + #echo "Removed docker-compose" ${__auto_sudo} mkdir -p /etc/apt/keyrings ${__auto_sudo} curl -fsSL https://download.docker.com/linux/debian/gpg | ${__auto_sudo} gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg ${__auto_sudo} echo \ @@ -1425,6 +1427,22 @@ __i_haz_web3signer() { fi } +__i_haz_keys_service() { + if ! docompose --profile tools config --services | grep -q validator-keys; then + if [[ "${1:-}" = "silent" ]]; then + return 1 + fi + echo "The validator-keys service is not defined. Are you running only a consensus layer client?" + echo "Key management happens on the validator client / web3signer, not the consensus layer client." + echo "You can however do things like send an exit message, prep a withdrawal credentials change," + echo "sign exit messages with ethdo." + echo "" + echo "Aborting." + exit 1 + fi + return 0 +} + __keys_usage() { echo "Call keymanager with an ACTION, one of:" echo " list" @@ -1468,8 +1486,12 @@ __keys_usage() { echo " send-address-change" echo " Send a change-operations.json with ethdo, setting the withdrawal address" echo + echo " sign-exit 0xPUBKEY" + echo " Create pre-signed exit message for the validator with public key 0xPUBKEY" echo " sign-exit from-keystore [--offline]" echo " Create pre-signed exit messages with ethdo, from keystore files in ./.eth/validator_keys" + echo " send-exit" + echo " Send pre-signed exit messages in ./.eth/exit_messages to the Ethereum chain" } keys() { @@ -1482,6 +1504,7 @@ keys() { __owner_uid=$(id -u "${OWNER}") if [ "${1:-}" = "import" ]; then + __i_haz_keys_service shift prep-keyimport "$@" if [ ${__non_interactive} = 1 ]; then @@ -1595,7 +1618,6 @@ keys() { created=0 failed=0 - mkdir -p .eth/exit_messages for __keyfile in .eth/validator_keys/keystore-*.json; do [ -f "${__keyfile}" ] || continue # Should always evaluate true - just in case if [ "${__justone}" -eq 0 ]; then @@ -1648,7 +1670,31 @@ keys() { if [ "${failed}" -gt 0 ]; then echo "Failed for ${failed} validators" fi + elif [ "${1:-}" = "send-exit" ] && ! __i_haz_keys_service silent; then + var="CL_NODE" + CL_NODE=$(sed -n -e "s/^${var}=\(.*\)/\1/p" ".env" || true) + network_name="$(basename "$(pwd)")_default" + if ! dodocker image ls --format "{{.Repository}}:{{.Tag}}" | grep -q "vc-utils:local"; then + if ! dpkg-query -W -f='${Status}' docker-ce 2>/dev/null | grep -q "ok installed"; then + dodocker build -t vc-utils:local ./vc-utils + else + if ! dpkg-query -W -f='${Status}' docker-buildx-plugin 2>/dev/null | grep -q "ok installed"; then + ${__auto_sudo} apt-get update && ${__auto_sudo} apt-get install -y docker-buildx-plugin + fi + dodocker buildx build -t vc-utils:local ./vc-utils + fi + fi + dodocker run --rm \ + -u 1000:1000 \ + --network "${network_name}" \ + --name send-exit \ + -v "$(pwd)/.eth/exit_messages:/exit_messages" \ + -v "/etc/localtime:/etc/localtime:ro" \ + -e "CL_NODE=${CL_NODE}" \ + --entrypoint "keymanager.sh" \ + vc-utils:local /var/lib/lighthouse/nonesuch.txt consensus send-exit else + __i_haz_keys_service docompose run --rm -e OWNER_UID="${__owner_uid}" validator-keys "$@" fi } diff --git a/lighthouse-vc-only.yml b/lighthouse-vc-only.yml index a7e0d909..86db8431 100644 --- a/lighthouse-vc-only.yml +++ b/lighthouse-vc-only.yml @@ -99,11 +99,13 @@ services: volumes: - lhvalidator-data:/var/lib/lighthouse - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} depends_on: - validator entrypoint: diff --git a/lighthouse.yml b/lighthouse.yml index 25ac3001..d3d44748 100644 --- a/lighthouse.yml +++ b/lighthouse.yml @@ -174,11 +174,13 @@ services: volumes: - lhvalidator-data:/var/lib/lighthouse - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} depends_on: - validator entrypoint: diff --git a/lodestar-vc-only.yml b/lodestar-vc-only.yml index ca9c3e69..dcdc6a2a 100644 --- a/lodestar-vc-only.yml +++ b/lodestar-vc-only.yml @@ -101,11 +101,13 @@ services: volumes: - lsvalidator-data:/var/lib/lodestar/validators - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} depends_on: - validator entrypoint: diff --git a/lodestar.yml b/lodestar.yml index e73dbb23..cc561f59 100644 --- a/lodestar.yml +++ b/lodestar.yml @@ -161,11 +161,13 @@ services: volumes: - lsvalidator-data:/var/lib/lodestar/validators - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} depends_on: - validator entrypoint: diff --git a/nimbus-vc-only.yml b/nimbus-vc-only.yml index 773d18dd..54333c17 100644 --- a/nimbus-vc-only.yml +++ b/nimbus-vc-only.yml @@ -65,11 +65,13 @@ services: volumes: - nimbus-vc-data:/var/lib/nimbus - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} depends_on: - validator entrypoint: diff --git a/nimbus.yml b/nimbus.yml index 906e0171..c0f7be8d 100644 --- a/nimbus.yml +++ b/nimbus.yml @@ -119,11 +119,13 @@ services: volumes: - nimbus-data:/var/lib/nimbus - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} depends_on: - consensus entrypoint: diff --git a/prysm-vc-only.yml b/prysm-vc-only.yml index 80dfc023..63afe617 100644 --- a/prysm-vc-only.yml +++ b/prysm-vc-only.yml @@ -153,11 +153,13 @@ services: volumes: - prysmvalidator-data:/var/lib/prysm - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} - PRYSM="true" depends_on: - validator diff --git a/prysm.yml b/prysm.yml index abf30c7f..18d465c5 100644 --- a/prysm.yml +++ b/prysm.yml @@ -220,11 +220,13 @@ services: volumes: - prysmvalidator-data:/var/lib/prysm - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} - PRYSM="true" depends_on: - validator diff --git a/teku-vc-only.yml b/teku-vc-only.yml index 8eeb0b8a..1ee0d452 100644 --- a/teku-vc-only.yml +++ b/teku-vc-only.yml @@ -84,12 +84,14 @@ services: volumes: - teku-data:/var/lib/teku - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - TLS="true" - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} depends_on: - validator entrypoint: diff --git a/teku.yml b/teku.yml index 9597850b..73c41282 100644 --- a/teku.yml +++ b/teku.yml @@ -125,12 +125,14 @@ services: volumes: - teku-data:/var/lib/teku - ./.eth/validator_keys:/validator_keys + - ./.eth/exit_messages:/exit_messages - /etc/localtime:/etc/localtime:ro environment: - TLS="true" - KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD:-} - KEY_API_PORT=${KEY_API_PORT:-7500} - WEB3SIGNER=${WEB3SIGNER:-false} + - CL_NODE=${CL_NODE} depends_on: - consensus entrypoint: diff --git a/vc-utils/keymanager.sh b/vc-utils/keymanager.sh index ec988006..408fc409 100755 --- a/vc-utils/keymanager.sh +++ b/vc-utils/keymanager.sh @@ -35,6 +35,31 @@ call_api() { fi } +call_cl_api() { + set +e + if [ -z "${__api_data}" ]; then + __code=$(curl -m 60 -s --show-error -o /tmp/result.txt -w "%{http_code}" -X "${__http_method}" -H "Accept: application/json" \ + "${CL_NODE}"/"${__api_path}") + else + __code=$(curl -m 60 -s --show-error -o /tmp/result.txt -w "%{http_code}" -X "${__http_method}" -H "Accept: application/json" -H "Content-Type: application/json" \ + --data "${__api_data}" "${CL_NODE}"/"${__api_path}") + fi + __return=$? + if [ $__return -ne 0 ]; then + echo "Error encountered while trying to call the consensus client REST API via curl." + echo "Please make sure the ${CL_NODE} URL is reachable." + echo "Error code $__return" + exit $__return + fi + if [ -f /tmp/result.txt ]; then + __result=$(cat /tmp/result.txt) + else + echo "Error encountered while trying to call the consensus client REST API via curl." + echo "HTTP code: ${__code}" + exit 1 + fi +} + get-token() { set +e if [ -z "${PRYSM:+x}" ]; then @@ -197,6 +222,68 @@ gas-delete() { esac } +exit-sign() { + if [ -z "$__pubkey" ]; then + echo "Please specify a validator public key" + exit 0 + fi + get-token + __api_path=eth/v1/validator/$__pubkey/voluntary_exit + __api_data="" + __http_method=POST + call_api + case $__code in + 200) echo "Signed voluntary exit for validator with public key $__pubkey";; + 400) echo "The pubkey or limit was formatted wrong. Error: $(echo "$__result" | jq -r '.message')"; exit 1;; + 401) echo "No authorization token found. This is a bug. Error: $(echo "$__result" | jq -r '.message')"; exit 1;; + 403) echo "The authorization token is invalid. Error: $(echo "$__result" | jq -r '.message')"; exit 1;; + 404) echo "Path not found error. Was that the right pubkey? Error: $(echo "$__result" | jq -r '.message')"; exit 0;; + 500) echo "Internal server error. Error: $(echo "$__result" | jq -r '.message')"; exit 1;; + *) echo "Unexpected return code $__code. Result: $__result"; exit 1;; + esac + # This is only reached for 200 + echo "${__result}" >"/exit_messages/${__pubkey::10}--${__pubkey:90}-exit.json" + exitstatus=$? + if [ "${exitstatus}" -eq 0 ]; then + echo "Writing the exit message into file ./.eth/exit_messages/${__pubkey::10}--${__pubkey:90}-exit.json succeeded" + else + echo "Error writing exit json to file ./.eth/exit_messages/${__pubkey::10}--${__pubkey:90}-exit.json" + fi +} + +exit-send() { + shopt -s nullglob + json_files=(/exit_messages/*.json) + + if [[ ${#json_files[@]} -eq 0 ]]; then + echo "No exit message files found in \"./.eth/exit_messages\"." + echo "Aborting." + exit 1 + fi + + for file in "${json_files[@]}"; do + validator_index=$(jq '.message.validator_index' "$file" 2>/dev/null || true) + + if [[ $validator_index != "null" && -n $validator_index ]]; then + __api_path=eth/v1/beacon/pool/voluntary_exits + __api_data="$(cat "${file}")" + __http_method=POST + call_cl_api + case $__code in + 200) echo "Loaded voluntary exit message for validator index $validator_index";; + 400) echo "Unable to load the voluntary exit message. Error: $(echo "$__result" | jq -r '.message')";; + 500) echo "Internal server error. Error: $(echo "$__result" | jq -r '.message')";; + *) echo "Unexpected return code $__code. Result: $__result";; + esac + echo "" + else + echo "./.eth/exit_messages/$(basename "$file") is not a pre-signed exit message." + echo "Skipping." + fi + done +} + + __validator-list-call() { __api_data="" __http_method=GET @@ -771,8 +858,12 @@ usage() { echo " send-address-change" echo " Send a change-operations.json with ethdo, setting the withdrawal address" echo + echo " sign-exit 0xPUBKEY" + echo " Create pre-signed exit message for the validator with public key 0xPUBKEY" echo " sign-exit from-keystore [--offline]" echo " Create pre-signed exit messages with ethdo, from keystore files in ./.eth/validator_keys" + echo " send-exit" + echo " Send pre-signed exit messages in ./.eth/exit_messages to the Ethereum chain" } set -e @@ -870,6 +961,13 @@ case "$3" in __pubkey=$4 gas-delete ;; + sign-exit) + __pubkey=$4 + exit-sign + ;; + send-exit) + exit-send + ;; prepare-address-change) echo "This should have been handled one layer up in ethd. This is a bug, please report." ;;