Skip to content

Instantly share code, notes, and snippets.

@alex-miller-0
Last active April 27, 2023 16:05
Show Gist options
  • Save alex-miller-0/f5e278e03b574c1e5f0e6e4d54cf1b49 to your computer and use it in GitHub Desktop.
Save alex-miller-0/f5e278e03b574c1e5f0e6e4d54cf1b49 to your computer and use it in GitHub Desktop.
Pre-sign validator exits as insurance against hardware failure for an ETH staker
# It may be a good idea to pre-sign exit messages so that if your staking setup
# encounters a hardware failure or goes down for some long period of time, you
# can safely exit and restrict penalties to only the few days in the exit queue.
# This script will sign (but NOT broadcast) exits for each of your validators
# and will write the result to a local file.
# NOTE: Be careful with this file. If someone obtains it, they can exit your
# validators for you (though they can't steal any money unless they have access
# to your withdrawal key).
# NOTE: This was designed for use with Lighthouse. It will need to be updated
# for use with other clients.
#
# USAGE: You can run this one of two ways:
# 1. Run without arguments to output a JSON file with signed exit messages
# from ALL known validators
# 2. Run with validator pubkey as command line argument to print a single
# signed exit message for that validator
#
from datetime import datetime, timezone
from math import trunc
import json
import os
import sys
import urllib.request
# Define the validator API location
VAL_API_URL = "http://127.0.0.1:" + (os.environ.get("VAL_API_PORT") or str(5062))
# Make an HTTP request
def HTTP_REQ(url, data=None, headers={}):
if data is not None:
# POST
req = urllib.request.Request(url, data=data, headers=headers)
else:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode("utf-8"))
return data
# Get the current UTC timestamp using system clock
def get_current_utc_timestamp():
dt = datetime.now(timezone.utc)
return trunc(dt.replace(tzinfo=timezone.utc).timestamp())
# Get the authorization token so that we can make protected request
# to the validator API. This secret should be in a local file.
def get_val_api_token():
token_loc = HTTP_REQ(VAL_API_URL + "/lighthouse/auth")["token_path"]
with open(token_loc) as f:
token = f.readline()
f.close()
return token
# Generate HTTP headers to send as authentication to the validator API
def get_val_api_headers():
token = get_val_api_token()
return { "Authorization": "Basic " + token }
# Get a set of currently known validator public keys
def get_validator_pubkeys():
headers = get_val_api_headers()
validators = HTTP_REQ(VAL_API_URL + "/lighthouse/validators", headers=headers)["data"]
val_pubkeys = []
for val in validators:
val_pubkeys.append(val["voting_pubkey"])
return val_pubkeys
# Generate a signed exit message
def sign_exit(pubkey):
headers = get_val_api_headers()
exit_msg = HTTP_REQ(VAL_API_URL + "/eth/v1/validator/" + pubkey + "/voluntary_exit", {}, headers)
return exit_msg
############
# The script
############
### OPTION 1: Sign exit for specific validator provided as command line arg ###
###############################################################################
# Check if the user provided a specific validator
if len(sys.argv) > 1:
print(json.dumps(sign_exit(sys.argv[1]), separators=(',', ':')))
sys.exit(0)
### OPTION 2: Sign exits for all known validators ###
#####################################################
# First, get our validators
validators = get_validator_pubkeys()
# Get all sigs
signed_msgs = []
for validator in validators:
signed_msgs.append(sign_exit(validator))
# Write to file
fname = "signed-exits-" + str(get_current_utc_timestamp()) + ".json"
f = open(fname, "w+")
f.write(json.dumps(signed_msgs, separators=(',', ':'))
f.close()
print("Successfully wrote " + str(len(validators)) + " signed exit messages to: " + fname)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment