Skip to content

Commit

Permalink
Support signing exit messages via keymanager API (eth-educators#1475)
Browse files Browse the repository at this point in the history
  • Loading branch information
yorickdowne authored Aug 10, 2023
1 parent 06ef38c commit d3b5ec7
Show file tree
Hide file tree
Showing 14 changed files with 170 additions and 5 deletions.
Empty file added .eth/exit_messages/.empty
Empty file.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 51 additions & 5 deletions ethd
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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 \
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions lighthouse-vc-only.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions lighthouse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions lodestar-vc-only.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions lodestar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions nimbus-vc-only.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions nimbus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions prysm-vc-only.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions prysm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions teku-vc-only.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions teku.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
98 changes: 98 additions & 0 deletions vc-utils/keymanager.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."
;;
Expand Down

0 comments on commit d3b5ec7

Please sign in to comment.