From 3cb34aee5e352cd86d577308d2ae23a8589a2d69 Mon Sep 17 00:00:00 2001 From: coincashew Date: Fri, 18 Oct 2024 01:43:20 -0300 Subject: [PATCH] Add Ephemery Testnet Support. Add Teku-Besu client pair. Add ethstaker-deposit-cli --- deploy-teku-besu.py | 771 +++++++++++++++++++++++++++++++++++++++ ethpillar.sh | 18 +- functions.sh | 15 +- install-teku-besu.sh | 185 ++++++++++ manage_validator_keys.sh | 67 ++-- resync_consensus.sh | 6 + 6 files changed, 1029 insertions(+), 33 deletions(-) create mode 100644 deploy-teku-besu.py create mode 100755 install-teku-besu.sh diff --git a/deploy-teku-besu.py b/deploy-teku-besu.py new file mode 100644 index 0000000..17e4a24 --- /dev/null +++ b/deploy-teku-besu.py @@ -0,0 +1,771 @@ +# Author: coincashew.eth | coincashew.com +# License: GNU GPL +# Source: https://github.com/coincashew/ethpillar +# +# Validator-Install: Standalone Teku BN + Standalone Teku VC + Besu EL + MEVboost +# Quickstart :: Minority Client :: Docker-free +# +# Made for home and solo stakers 🏠🥩 +# +# Acknowledgments +# Validator-Install is branched from validator-install written by Accidental-green: https://github.com/accidental-green/validator-install +# The groundwork for this project was established through their previous efforts. + +import os +import requests +import re +import fnmatch +import random +import json +import tarfile +import shutil +import subprocess +import tempfile +import urllib.request +import zipfile +import random +import sys +import platform +from consolemenu import * +from consolemenu.items import * +import argparse +from dotenv import load_dotenv, dotenv_values +from config import * + +def clear_screen(): + if os.name == 'posix': # Unix-based systems (e.g., Linux, macOS) + os.system('clear') + elif os.name == 'nt': # Windows + os.system('cls') + +clear_screen() # Call the function to clear the screen + +# Valid configurations +valid_networks = ['MAINNET', 'HOLESKY', 'SEPOLIA', 'EPHEMERY'] +valid_exec_clients = ['BESU'] +valid_consensus_clients = ['TEKU'] +valid_install_configs = ['Solo Staking Node', 'Full Node Only', 'Lido CSM Staking Node', 'Validator Client Only', 'Failover Staking Node'] + +# Load environment variables from env file +load_dotenv("env") + +# Set options to parsed arguments +EL_P2P_PORT=os.getenv('EL_P2P_PORT') +EL_RPC_PORT=os.getenv('EL_RPC_PORT') +EL_MAX_PEER_COUNT=os.getenv('EL_MAX_PEER_COUNT') +CL_P2P_PORT=os.getenv('CL_P2P_PORT') +CL_REST_PORT=os.getenv('CL_REST_PORT') +CL_MAX_PEER_COUNT=os.getenv('CL_MAX_PEER_COUNT') +CL_IP_ADDRESS=os.getenv('CL_IP_ADDRESS') +JWTSECRET_PATH=os.getenv('JWTSECRET_PATH') +GRAFFITI=os.getenv('GRAFFITI') +FEE_RECIPIENT_ADDRESS=os.getenv('FEE_RECIPIENT_ADDRESS') +MEV_MIN_BID=os.getenv('MEV_MIN_BID') +CSM_FEE_RECIPIENT_ADDRESS_MAINNET=os.getenv('CSM_FEE_RECIPIENT_ADDRESS_MAINNET') +CSM_FEE_RECIPIENT_ADDRESS_HOLESKY=os.getenv('CSM_FEE_RECIPIENT_ADDRESS_HOLESKY') +CSM_GRAFFITI=os.getenv('CSM_GRAFFITI') +CSM_MEV_MIN_BID=os.getenv('CSM_MEV_MIN_BID') +CSM_WITHDRAWAL_ADDRESS_MAINNET=os.getenv('CSM_WITHDRAWAL_ADDRESS_MAINNET') +CSM_WITHDRAWAL_ADDRESS_HOLESKY=os.getenv('CSM_WITHDRAWAL_ADDRESS_HOLESKY') + +# Create argparse options +parser = argparse.ArgumentParser(description='Validator Install Options :: CoinCashew.com',formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument("--network", type=str, help="Sets the Ethereum network", choices=valid_networks, default="") +parser.add_argument("--jwtsecret", type=str,help="Sets the jwtsecret file", default=JWTSECRET_PATH) +parser.add_argument("--graffiti", type=str, help="Sets the validator graffiti message", default=GRAFFITI) +parser.add_argument("--fee_address", type=str, help="Sets the fee recipient address", default="") +parser.add_argument("--el_p2p_port", type=int, help="Sets the Execution Client's P2P Port", default=EL_P2P_PORT) +parser.add_argument("--el_rpc_port", type=int, help="Sets the Execution Client's RPC Port", default=EL_RPC_PORT) +parser.add_argument("--el_max_peers", type=int, help="Sets the Execution Client's max peer count", default=EL_MAX_PEER_COUNT) +parser.add_argument("--cl_p2p_port", type=int, help="Sets the Consensus Client's P2P Port", default=CL_P2P_PORT) +parser.add_argument("--cl_rest_port", type=int, help="Sets the Consensus Client's REST Port", default=CL_REST_PORT) +parser.add_argument("--cl_max_peers", type=int, help="Sets the Consensus Client's max peer count", default=CL_MAX_PEER_COUNT) +parser.add_argument("--vc_only_bn_address", type=str, help="Sets Validator Only configuration's (beacon node) IP address, e.g. http://192.168.1.123:5052") +parser.add_argument("--skip_prompts", type=str, help="Performs non-interactive installation. Skips any interactive prompts if set to true", default="") +parser.add_argument("--install_config", type=str, help="Sets the node installation configuration", choices=valid_install_configs, default="") +parser.add_argument("-v", "--version", action="version", version="%(prog)s 1.0.0") +args = parser.parse_args() +#print(args) + +def get_machine_architecture(): + machine_arch=platform.machine() + if machine_arch == "x86_64": + return "amd64" + elif machine_arch == "aarch64": + return "arm64" + else: + print(f'Unsupported machine architecture: {machine_arch}') + exit(1) + +def get_computer_platform(): + platform_name=platform.system() + if platform_name == "Linux": + return platform_name + else: + print(f'Unsupported platform: {platform_name}') + exit(1) + +binary_arch=get_machine_architecture() +platform_arch=get_computer_platform() + +# Change to the home folder +os.chdir(os.path.expanduser("~")) + +if not args.network and not args.skip_prompts: + # Ask the user for Ethereum network + index = SelectionMenu.get_selection(valid_networks,title='Validator Install Quickstart :: CoinCashew.com',subtitle='Installs Besu EL / Teku BN / Teku VC / MEVboost\nSelect Ethereum network:') + + # Exit selected + if index == 4: + exit(0) + + # Set network + eth_network=valid_networks[index] + eth_network=eth_network.lower() +else: + eth_network=args.network.lower() + +if not args.install_config and not args.skip_prompts: + # Sepolia can only be full node + if eth_network == "sepolia": + install_config=valid_install_configs[1] + else: + # Ask the user for installation config + index = SelectionMenu.get_selection(valid_install_configs,title='Validator Install Quickstart :: CoinCashew.com',subtitle='What type of installation would you like?\nSelect your type:',show_exit_option=False) + # Set install configuration + install_config=valid_install_configs[index] +else: + install_config=args.install_config + +# Sepolia is a permissioned validator set, default to NODE_ONLY +if eth_network == "sepolia": + NODE_ONLY=True + MEVBOOST_ENABLED=False + VALIDATOR_ENABLED=False + VALIDATOR_ONLY=False +else: + match install_config: + case "Solo Staking Node": + NODE_ONLY=False + MEVBOOST_ENABLED=True + VALIDATOR_ENABLED=True + VALIDATOR_ONLY=False + case "Full Node Only": + NODE_ONLY=True + MEVBOOST_ENABLED=False + VALIDATOR_ENABLED=False + VALIDATOR_ONLY=False + case "Lido CSM Staking Node": + NODE_ONLY=False + MEVBOOST_ENABLED=True + VALIDATOR_ENABLED=True + VALIDATOR_ONLY=False + if eth_network == "mainnet": + FEE_RECIPIENT_ADDRESS=CSM_FEE_RECIPIENT_ADDRESS_MAINNET + CSM_WITHDRAWAL_ADDRESS=CSM_WITHDRAWAL_ADDRESS_MAINNET + elif eth_network == "holesky": + FEE_RECIPIENT_ADDRESS=CSM_FEE_RECIPIENT_ADDRESS_HOLESKY + CSM_WITHDRAWAL_ADDRESS=CSM_WITHDRAWAL_ADDRESS_HOLESKY + elif eth_network == "ephemery": + FEE_RECIPIENT_ADDRESS=CSM_FEE_RECIPIENT_ADDRESS_HOLESKY + CSM_WITHDRAWAL_ADDRESS=CSM_WITHDRAWAL_ADDRESS_HOLESKY + else: + print(f'Unsupported Lido CSM Staking Node network: {eth_network}') + exit(1) + GRAFFITI=CSM_GRAFFITI + MEV_MIN_BID=CSM_MEV_MIN_BID + case "Validator Client Only": + NODE_ONLY=False + MEVBOOST_ENABLED=True + VALIDATOR_ENABLED=True + VALIDATOR_ONLY=True + case "Failover Staking Node": + NODE_ONLY=False + MEVBOOST_ENABLED=True + VALIDATOR_ENABLED=False + VALIDATOR_ONLY=False + +# Ephemery override, always turn off mevboost +if eth_network == "ephemery": + MEVBOOST_ENABLED=False + +execution_client="" +consensus_client="" + +if not VALIDATOR_ONLY: + # Set clients to besu + execution_client = valid_exec_clients[0] + execution_client = execution_client.lower() + +# Set clients to teku +consensus_client = valid_consensus_clients[0] +# Set to lowercase +consensus_client = consensus_client.lower() + + +# Validates an eth address +def is_valid_eth_address(address): + pattern = re.compile("^0x[a-fA-F0-9]{40}$") + return bool(pattern.match(address)) + +# Set FEE_RECIPIENT_ADDRESS +if not NODE_ONLY and FEE_RECIPIENT_ADDRESS == "" and not args.skip_prompts: + # Prompt User for validator tips address + while True: + FEE_RECIPIENT_ADDRESS = Screen().input(f'Enter your Ethereum address (aka Fee Recipient Address)\n Hints: \n - Use ETH adddress from a hardware wallet.\n - Do not use an exchange address.\n > ') + if is_valid_eth_address(FEE_RECIPIENT_ADDRESS): + print("Valid Ethereum address") + break + else: + print("Invalid Ethereum address. Try again.") + + +# Validates an CL beacon node address with port +def validate_beacon_node_address(ip_port): + pattern = r"^(http|https|ws):\/\/((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:?\d{1,5})?$" + if re.match(pattern, ip_port): + return True + else: + return False + +BN_ADDRESS="" +# Set BN_ADDRESS +if VALIDATOR_ONLY and args.vc_only_bn_address is None and not args.skip_prompts: + # Prompt User for beacon node address + while True: + BN_ADDRESS = Screen().input(f'\nEnter your consensus client (beacon node) address.\nExample: http://192.168.1.123:5052\n > ') + if validate_beacon_node_address(BN_ADDRESS): + print("Valid beacon node address") + break + else: + print("Invalid beacon node address. Try again.") +else: + BN_ADDRESS=args.vc_only_bn_address + + + +if not args.skip_prompts: + # Format confirmation message + if install_config == "Solo Staking Node" or install_config == "Lido CSM Staking Node" or install_config == "Failover Staking Node": + message=f'\nConfirmation: Verify your settings\n\nNetwork: {eth_network.upper()}\nInstallation configuration: {install_config}\nFee Recipient Address: {FEE_RECIPIENT_ADDRESS}\n\nIs this correct?' + elif install_config == "Full Node Only": + message=f'\nConfirmation: Verify your settings\n\nNetwork: {eth_network.upper()}\nInstallation configuration: {install_config}\n\nIs this correct?' + elif install_config == "Validator Client Only": + message=f'\nConfirmation: Verify your settings\n\nNetwork: {eth_network.upper()}\nInstallation configuration: {install_config}\nConsensus client (beacon node) address: {BN_ADDRESS}\n\nIs this correct?' + else: + print(f"\nError: Unknown install_config") + exit(1) + + answer=PromptUtils(Screen()).prompt_for_yes_or_no(f'{message}') + + if not answer: + file_name = os.path.basename(sys.argv[0]) + print(f'\nInstall cancelled by user. \n\nWhen ready, re-run install command:\npython3 {file_name}') + exit(0) + +# Initialize sync urls for selected network +if eth_network == "mainnet": + sync_urls = mainnet_sync_urls +elif eth_network == "holesky": + sync_urls = holesky_sync_urls +elif eth_network == "sepolia": + sync_urls = sepolia_sync_urls +elif eth_network == "ephemery": + sync_urls = ephemery_sync_urls + +# Use a random sync url +sync_url = random.choice(sync_urls)[1] + +def setup_node(): + if not VALIDATOR_ONLY: + # Create JWT directory + subprocess.run([f'sudo mkdir -p $(dirname {JWTSECRET_PATH})'], shell=True) + + # Generate random hex string and save to file + rand_hex = subprocess.run(['openssl', 'rand', '-hex', '32'], stdout=subprocess.PIPE) + subprocess.run([f'sudo tee {JWTSECRET_PATH}'], input=rand_hex.stdout, stdout=subprocess.DEVNULL, shell=True) + + # Update and upgrade packages + subprocess.run(['sudo', 'apt', '-y', '-qq', 'update']) + subprocess.run(['sudo', 'apt', '-y', '-qq', 'upgrade']) + + # Autoremove packages + subprocess.run(['sudo', 'apt', '-y', '-qq' , 'autoremove']) + + # Chrony timesync package + subprocess.run(['sudo', 'apt', '-y', '-qq', 'install', 'chrony']) + +def install_mevboost(): + if MEVBOOST_ENABLED == True and not VALIDATOR_ONLY: + # Step 1: Create mevboost service account + os.system("sudo useradd --no-create-home --shell /bin/false mevboost") + + # Step 2: Install mevboost + # Change to the home folder + os.chdir(os.path.expanduser("~")) + + # Define the Github API endpoint to get the latest release + url = 'https://api.github.com/repos/flashbots/mev-boost/releases/latest' + + # Send a GET request to the API endpoint + response = requests.get(url) + global mevboost_version + mevboost_version = response.json()['tag_name'] + + # Search for the asset with the name that ends in {platform_arch}_{binary_arch}.tar.gz + assets = response.json()['assets'] + download_url = None + for asset in assets: + if asset['name'].endswith(f'{platform_arch.lower()}_{binary_arch}.tar.gz'): + download_url = asset['browser_download_url'] + break + + if download_url is None: + print("Error: Could not find the download URL for the latest release.") + exit(1) + + # Download the latest release binary + print(f">> Downloading mevboost > URL: {download_url}") + + try: + # Download the file + response = requests.get(download_url, stream=True) + response.raise_for_status() # Raise an exception for HTTP errors + + # Save the binary to the home folder + with open("mev-boost.tar.gz", "wb") as f: + for chunk in response.iter_content(1024): + if chunk: + f.write(chunk) + + print(f">> Successfully downloaded: {asset['name']}") + + except requests.exceptions.RequestException as e: + print(f"Error: Unable to download file. Try again later. {e}") + exit(1) + + # Extract the binary to the home folder + with tarfile.open('mev-boost.tar.gz', 'r:gz') as tar: + tar.extractall() + + # Move the binary to /usr/local/bin using sudo + os.system(f"sudo mv mev-boost /usr/local/bin") + + # Remove files + os.system(f"rm mev-boost.tar.gz LICENSE README.md") + + ##### MEV Boost Service File + mev_boost_service_file_lines = [ + '[Unit]', + f'Description=MEV-Boost Service for {eth_network.upper()}', + 'Wants=network-online.target', + 'After=network-online.target', + 'Documentation=https://www.coincashew.com', + '', + '[Service]', + 'User=mevboost', + 'Group=mevboost', + 'Type=simple', + 'Restart=always', + 'RestartSec=5', + 'ExecStart=/usr/local/bin/mev-boost \\', + f' -{eth_network} \\', + f' -min-bid {MEV_MIN_BID} \\', + ' -relay-check \\', + ] + + if eth_network == 'mainnet': + relay_options=mainnet_relay_options + elif eth_network == 'holesky': + relay_options=holesky_relay_options + else: + relay_options=sepolia_relay_options + + for relay in relay_options: + relay_line = f' -relay {relay["url"]} \\' + mev_boost_service_file_lines.append(relay_line) + + # Remove the trailing '\\' from the last relay line + mev_boost_service_file_lines[-1] = mev_boost_service_file_lines[-1].rstrip(' \\') + + mev_boost_service_file_lines.extend([ + '', + '[Install]', + 'WantedBy=multi-user.target', + ]) + mev_boost_service_file = '\n'.join(mev_boost_service_file_lines) + + mev_boost_temp_file = 'mev_boost_temp.service' + global mev_boost_service_file_path + mev_boost_service_file_path = '/etc/systemd/system/mevboost.service' + + with open(mev_boost_temp_file, 'w') as f: + f.write(mev_boost_service_file) + + os.system(f'sudo cp {mev_boost_temp_file} {mev_boost_service_file_path}') + os.remove(mev_boost_temp_file) + +def download_and_install_besu(): + if execution_client == 'besu': + # Create User and directories + subprocess.run(["sudo", "useradd", "--no-create-home", "--shell", "/bin/false", "execution"]) + subprocess.run(["sudo", "mkdir", "-p", "/var/lib/besu"]) + subprocess.run(["sudo", "chown", "-R", "execution:execution", "/var/lib/besu"]) + print(f">> Installing dependencies") + subprocess.run(["sudo", "apt-get", '-qq', "install", "openjdk-21-jdk", "libjemalloc-dev", "-y"], check=True) + + # Define the Github API endpoint to get the latest release + url = 'https://api.github.com/repos/hyperledger/besu/releases/latest' + + # Send a GET request to the API endpoint + response = requests.get(url) + global besu_version + besu_version = response.json()['tag_name'] + + assets = response.json()['assets'] + download_url = None + for asset in assets: + if asset['name'].endswith(f'besu-{besu_version}.tar.gz'): + download_url = asset['browser_download_url'] + break + + if download_url is None: + print("Error: Could not find the download URL for the latest release.") + exit(1) + + # Download the latest release binary + print(f">> Downloading Besu > URL: {download_url}") + + try: + # Download the file + response = requests.get(download_url, stream=True) + response.raise_for_status() # Raise an exception for HTTP errors + + # Save the binary to the home folder + with open("besu.tar.gz", "wb") as f: + for chunk in response.iter_content(1024): + if chunk: + f.write(chunk) + + print(f">> Successfully downloaded: {asset['name']}") + + except requests.exceptions.RequestException as e: + print(f"Error: Unable to download file. Try again later. {e}") + exit(1) + + # Extract the binary to the home folder + with tarfile.open('besu.tar.gz', 'r:gz') as tar: + tar.extractall() + + # Find the extracted folder + extracted_folder = None + for item in os.listdir(): + if item.startswith(f'besu-'): + extracted_folder = item + break + + if extracted_folder is None: + print("Error: Could not find the extracted folder.") + exit(1) + + # Move the binary to /usr/local/bin using sudo + os.system(f"sudo mv {extracted_folder} ~/besu") + os.system(f"sudo mv ~/besu /usr/local/bin/besu") + + # Remove the besu.tar.gz file + os.remove('besu.tar.gz') + + ##### BESU SERVICE FILE ########### + besu_service_file = f'''[Unit] +Description=Besu Execution Layer Client service for {eth_network.upper()} +After=network-online.target +Wants=network-online.target +Documentation=https://www.coincashew.com + +[Service] +Type=simple +User=execution +Group=execution +Restart=on-failure +RestartSec=3 +KillSignal=SIGINT +TimeoutStopSec=900 +Environment="JAVA_OPTS=-Xmx5g" +ExecStart=/usr/local/bin/besu/bin/besu --network={eth_network} --p2p-port={EL_P2P_PORT} --rpc-http-port={EL_RPC_PORT} --engine-rpc-port=8551 --max-peers={EL_MAX_PEER_COUNT} --metrics-enabled=true --metrics-port=6060 --rpc-http-enabled=true --sync-mode=SNAP --data-storage-format=BONSAI --data-path="/var/lib/besu" --engine-jwt-secret={JWTSECRET_PATH} + +[Install] +WantedBy=multi-user.target +''' + + besu_temp_file = 'execution_temp.service' + global besu_service_file_path + besu_service_file_path = '/etc/systemd/system/execution.service' + + with open(besu_temp_file, 'w') as f: + f.write(besu_service_file) + + os.system(f'sudo cp {besu_temp_file} {besu_service_file_path}') + + os.remove(besu_temp_file) + +def download_teku(): + if consensus_client == 'teku': + # Change to the home folder + os.chdir(os.path.expanduser("~")) + + # Define the Github API endpoint to get the latest release + url = 'https://api.github.com/repos/ConsenSys/teku/releases/latest' + + # Send a GET request to the API endpoint + response = requests.get(url) + global teku_version + teku_version = response.json()['tag_name'] + download_url = f'https://artifacts.consensys.net/public/teku/raw/names/teku.tar.gz/versions/{teku_version}/teku-{teku_version}.tar.gz' + + if download_url is None: + print("Error: Could not find the download URL for the latest release.") + exit(1) + + # Download the latest release binary + print(f">> Downloading Teku > URL: {download_url}") + + try: + # Download the file + response = requests.get(download_url, stream=True) + response.raise_for_status() # Raise an exception for HTTP errors + + # Save the binary to the home folder + with open("teku.tar.gz", "wb") as f: + for chunk in response.iter_content(1024): + if chunk: + f.write(chunk) + + print(f">> Successfully downloaded: teku-{teku_version}.tar.gz") + + except requests.exceptions.RequestException as e: + print(f"Error: Unable to download file. Try again later. {e}") + exit(1) + + # Extract the binary to the home folder + with tarfile.open('teku.tar.gz', 'r:gz') as tar: + tar.extractall() + + # Find the extracted folder + extracted_folder = None + for item in os.listdir(): + if item.startswith(f'teku-'): + extracted_folder = item + break + + if extracted_folder is None: + print("Error: Could not find the extracted folder.") + exit(1) + + # Move the binary to /usr/local/bin using sudo + os.system(f"sudo mv {extracted_folder} ~/teku") + os.system(f"sudo mv ~/teku /usr/local/bin/teku") + + # Remove the teku.tar.gz file and extracted folder + os.remove('teku.tar.gz') + +def install_teku(): + if consensus_client == 'teku' and not VALIDATOR_ONLY: + # Create data paths, service user, assign ownership permissions + subprocess.run(['sudo', 'mkdir', '-p', '/var/lib/teku']) + subprocess.run(['sudo', 'chmod', '700', '/var/lib/teku']) + subprocess.run(['sudo', 'useradd', '--no-create-home', '--shell', '/bin/false', 'consensus']) + subprocess.run(['sudo', 'chown', '-R', 'consensus:consensus', '/var/lib/teku']) + print(f">> Installing dependencies") + subprocess.run(["sudo", "apt-get", '-qq', "install", "openjdk-21-jdk", "libsnappy-dev", "libc6-dev", "-y"], check=True) + + if MEVBOOST_ENABLED == True: + _mevparameters='--validators-builder-registration-default-enabled=true --builder-endpoint=http://127.0.0.1:18550' + else: + _mevparameters='' + + if VALIDATOR_ENABLED == True and FEE_RECIPIENT_ADDRESS: + _feeparameters=f'--validators-proposer-default-fee-recipient={FEE_RECIPIENT_ADDRESS}' + else: + _feeparameters='' + + ########### Teku SERVICE FILE ############# + teku_service_file = f'''[Unit] +Description=Teku Beacon Node Consensus Client service for {eth_network.upper()} +Wants=network-online.target +After=network-online.target +Documentation=https://www.coincashew.com + +[Service] +Type=simple +User=consensus +Group=consensus +Restart=on-failure +RestartSec=3 +KillSignal=SIGINT +TimeoutStopSec=900 +Environment=JAVA_OPTS=-Xmx6g +Environment=TEKU_OPTS=-XX:-HeapDumpOnOutOfMemoryError +ExecStart=/usr/local/bin/teku/bin/teku --network={eth_network} --data-path=/var/lib/teku --data-storage-mode=minimal --initial-state={sync_url} --ee-endpoint=http://127.0.0.1:8551 --ee-jwt-secret-file={JWTSECRET_PATH} --rest-api-enabled=true --rest-api-port={CL_REST_PORT} --p2p-port={CL_P2P_PORT} --p2p-peer-upper-bound={CL_MAX_PEER_COUNT} --metrics-enabled=true --metrics-port=8008 {_feeparameters} {_mevparameters} + +[Install] +WantedBy=multi-user.target +''' + teku_temp_file = 'consensus_temp.service' + global teku_service_file_path + teku_service_file_path = '/etc/systemd/system/consensus.service' + + with open(teku_temp_file, 'w') as f: + f.write(teku_service_file) + + os.system(f'sudo cp {teku_temp_file} {teku_service_file_path}') + os.remove(teku_temp_file) + +def install_teku_validator(): + if MEVBOOST_ENABLED == True: + _mevparameters='--validators-builder-registration-default-enabled=true' + else: + _mevparameters='' + + if VALIDATOR_ENABLED == True and FEE_RECIPIENT_ADDRESS: + _feeparameters=f'--validators-proposer-default-fee-recipient={FEE_RECIPIENT_ADDRESS}' + else: + _feeparameters='' + + if BN_ADDRESS: + _beaconnodeparameters=f'--beacon-node-api-endpoint={BN_ADDRESS}' + else: + _beaconnodeparameters=f'--beacon-node-api-endpoint=http://{CL_IP_ADDRESS}:{CL_REST_PORT}' + + if consensus_client == 'teku' and VALIDATOR_ENABLED == True: + # Create data paths, service user, assign ownership permissions + subprocess.run(['sudo', 'mkdir', '-p', '/var/lib/teku_validator']) + subprocess.run(['sudo', 'chmod', '700', '/var/lib/teku_validator']) + subprocess.run(['sudo', 'useradd', '--no-create-home', '--shell', '/bin/false', 'validator']) + subprocess.run(['sudo', 'chown', '-R', 'validator:validator', '/var/lib/teku_validator']) + + teku_validator_file = f'''[Unit] +Description=Teku Validator Client service for {eth_network.upper()} +Wants=network-online.target +After=network-online.target +Documentation=https://www.coincashew.com + +[Service] +Type=simple +User=validator +Group=validator +Restart=on-failure +RestartSec=3 +KillSignal=SIGINT +TimeoutStopSec=900 +ExecStart=/usr/local/bin/teku/bin/teku validator-client --network={eth_network} --data-path=/var/lib/teku_validator --validator-keys=/var/lib/teku_validator/validator_keys:/var/lib/teku_validator/validator_keys --metrics-enabled=true --metrics-port=8009 --validators-graffiti={GRAFFITI} {_beaconnodeparameters} {_feeparameters} {_mevparameters} + +[Install] +WantedBy=multi-user.target +''' + teku_temp_file = 'validator_temp.service' + global teku_validator_file_path + teku_validator_file_path = '/etc/systemd/system/validator.service' + + with open(teku_temp_file, 'w') as f: + f.write(teku_validator_file) + + os.system(f'sudo cp {teku_temp_file} {teku_validator_file_path}') + os.remove(teku_temp_file) + +def finish_install(): + # Reload the systemd daemon + subprocess.run(['sudo', 'systemctl', 'daemon-reload']) + + print(f'##########################\n') + print(f'## Installation Summary ##\n') + print(f'##########################\n') + + print(f'Installation Configuration: \n{install_config}\n') + + if execution_client == 'besu': + print(f'Besu Version: \n{besu_version}\n') + + if consensus_client == 'teku': + print(f'Teku Version: \n{teku_version}\n') + + if MEVBOOST_ENABLED and not VALIDATOR_ONLY: + print(f'Mevboost Version: \n{mevboost_version}\n') + + print(f'Network: {eth_network.upper()}\n') + + if not VALIDATOR_ONLY: + print(f'CheckPointSyncURL: {sync_url}\n') + + if VALIDATOR_ONLY and BN_ADDRESS: + print(f'Beacon Node Address: {BN_ADDRESS}\n') + os.chdir(os.path.expanduser("~/git/ethpillar")) + os.system(f'cp .env.overrides.example .env.overrides') + + if NODE_ONLY == False: + print(f'Validator Fee Recipient Address: {FEE_RECIPIENT_ADDRESS}\n') + + print(f'Systemd service files created:') + if not VALIDATOR_ONLY: + print(f'\n{teku_service_file_path}\n{besu_service_file_path}') + if VALIDATOR_ENABLED == True: + print(f'{teku_validator_file_path}') + if MEVBOOST_ENABLED == True and not VALIDATOR_ONLY: + print(f'{mev_boost_service_file_path}') + + if args.skip_prompts: + print(f'\nNon-interactive install successful! Skipped prompts.') + exit(0) + + # Prompt to start services + if not VALIDATOR_ONLY: + answer=PromptUtils(Screen()).prompt_for_yes_or_no(f"\nInstallation successful!\nSyncing a Teku/Besu node for validator duties can be as quick as a few hours.\nWould you like to start syncing now?") + if answer: + os.system(f'sudo systemctl start execution consensus') + if MEVBOOST_ENABLED == True: + os.system(f'sudo systemctl start mevboost') + + answer=PromptUtils(Screen()).prompt_for_yes_or_no(f"\nConfigure node to autostart:\nWould you like this node to autostart when system boots up?") + + # Prompt to enable autostart services + if answer: + if not VALIDATOR_ONLY: + os.system(f'sudo systemctl enable execution consensus') + if VALIDATOR_ENABLED == True: + os.system(f'sudo systemctl enable validator') + if MEVBOOST_ENABLED == True and not VALIDATOR_ONLY: + os.system(f'sudo systemctl enable mevboost') + + # Ask CSM staker if they to manage validator keystores + if install_config == 'Lido CSM Staking Node': + answer=PromptUtils(Screen()).prompt_for_yes_or_no(f"\nWould you like to generate or import new Lido CSM validator keys now?\nReminder: Set the Lido withdrawal address to: {CSM_WITHDRAWAL_ADDRESS}") + if answer: + os.chdir(os.path.expanduser("~/git/ethpillar")) + command = './manage_validator_keys.sh' + subprocess.run(command) + + # Ask solo staker if they to manage validator keystores + if install_config == 'Solo Staking Node' or install_config == 'Validator Client Only': + answer=PromptUtils(Screen()).prompt_for_yes_or_no(f"\nWould you like to generate or import validator keys now?\nIf not, resume at: ethpillar > Validator Client ") + if answer: + os.chdir(os.path.expanduser("~/git/ethpillar")) + command = './manage_validator_keys.sh' + subprocess.run(command) + + # Failover staking node reminders + if install_config == 'Failover Staking Node': + print(f'\nReminder for Failover Staking Node configurations:\n1. Consensus Client: Expose consensus client RPC port\n2. UFW Firewall: Update to allow incoming traffic on port {CL_REST_PORT}\n3. UFW firewall: Whitelist the validator(s) IP address.') + + # Validator Client Only overrides + if install_config == 'Validator Client Only': + answer=PromptUtils(Screen()).prompt_for_yes_or_no(f"Would you like update your EL/CL override settings now?\nYour validator client needs to know EL/CL settings.\nIf not, update later at\nEthPillar > System Administration > Override environment variables.") + if answer: + command = ['nano', '~/git/ethpillar/.env.overrides'] + subprocess.run(command) + +setup_node() +install_mevboost() +download_and_install_besu() +download_teku() +install_teku() +install_teku_validator() +finish_install() diff --git a/ethpillar.sh b/ethpillar.sh index f4cf40e..22d268a 100755 --- a/ethpillar.sh +++ b/ethpillar.sh @@ -12,7 +12,7 @@ # 🙌 Ask questions on Discord: # * https://discord.gg/dEpAVWgFNB -EP_VERSION="2.1.4" +EP_VERSION="2.2.0" # VARIABLES export BASE_DIR="$HOME/git/ethpillar" && cd $BASE_DIR @@ -510,7 +510,7 @@ while true; do 22) if whiptail --title "Switch Networks" --defaultno --yesno "Are you sure you want to switch networks?\nAll current node data will be removed." 9 78; then if runScript uninstall.sh; then - runScript install-nimbus-nethermind.sh true + installNode whiptail --title "Switch Networks" --msgbox "Completed network switching process." 8 78 fi fi @@ -1020,7 +1020,19 @@ function checkV1StakingSetup(){ # If no consensus or validator client service is installed, start install workflow function installNode(){ if [[ ! -f /etc/systemd/system/consensus.service && ! -f /etc/systemd/system/validator.service ]]; then - runScript install-nimbus-nethermind.sh true + _CLIENTCOMBO=$(whiptail --title "Choose your consensus and execution clients" --menu \ + "Pick your combination:" 10 78 2 \ + "Nimbus-Nethermind" "lightweight. secure. easy to use. nim and .net" \ + "Teku-Besu" "ephemery testnet ready. enterprise grade. java" \ + 3>&1 1>&2 2>&3) + case $_CLIENTCOMBO in + Nimbus-Nethermind) + runScript install-nimbus-nethermind.sh true + ;; + Teku-Besu) + runScript install-teku-besu.sh true + ;; + esac fi } diff --git a/functions.sh b/functions.sh index b0e9ee9..23fe7fb 100755 --- a/functions.sh +++ b/functions.sh @@ -191,6 +191,9 @@ getNetwork(){ 11155111) NETWORK="Sepolia" ;; + 39438138) + NETWORK="Ephemery" + ;; *) NETWORK="Custom Network" esac @@ -711,6 +714,8 @@ createBeaconChainDashboardLink(){ _link="https://holesky.beaconcha.in/dashboard?validators=" ;; mainnet) _link="https://beaconcha.in/dashboard?validators=" ;; + ephemery) + _link="https://beaconchain.ephemery.dev/dashboard?validators=" ;; *) echo "Unsupported Network: ${NETWORK}" && exit 1 esac @@ -749,7 +754,7 @@ testYetAnotherBenchScript(){ echo " * Bandwidth should be at least 10Mbit/s upload and 10Mbit/s download" echo " * At least 2TB data transfer per month" echo "- Disk:" - echo " * Capacity at least 2TB Mainnet, 300GB Holesky testnet" + echo " * Capacity at least 2TB Mainnet, 300GB Holesky testnet, 3GB Ephemery testnet" echo " * NVME drive preferred, SSD with TLC cache can work" echo " * I/O Per Second on 4k block size test at least 15K IOPS read, 5K IOPS write" echo "- CPU:" @@ -944,6 +949,7 @@ checkValidatorQueue(){ declare -A BEACONCHAIN_URLS=() BEACONCHAIN_URLS["Mainnet"]="https://beaconcha.in" BEACONCHAIN_URLS["Holesky"]="https://holesky.beaconcha.in" + BEACONCHAIN_URLS["Ephemery"]="https://beaconchain.ephemery.dev" # Dencun entry churn cap CHURN_ENTRY_PER_EPOCH=8 CHURN_RATE_CONSTANT=65536 @@ -970,7 +976,12 @@ checkValidatorQueue(){ echo "Churn: ${CHURN_ENTRY_PER_EPOCH} per epoch" ohai "Exit Queue" echo "Validators Exiting: $(echo $json | jq -r '.data.beaconchain_exiting')" - echo "Estimated wait time: $(echo "scale=1; $(echo "$json" | jq -r '.data.beaconchain_exiting') / $CHURN_EXIT_PER_DAY" | bc) days" + if [[ $(echo $json | jq -r '.data.beaconchain_exiting') -gt 0 ]]; then + _waittime=$(echo "scale=1; $(echo "$json" | jq -r '.data.beaconchain_exiting') / $CHURN_EXIT_PER_DAY" | bc) + else + _waittime="0" + fi + echo "Estimated wait time: $_waittime days" echo "Churn: ${CHURN_EXIT_PER_EPOCH} per epoch" ohai "Total Active Validator Count: $(echo $json | jq -r '.data.validatorscount')" else diff --git a/install-teku-besu.sh b/install-teku-besu.sh new file mode 100755 index 0000000..722b6e5 --- /dev/null +++ b/install-teku-besu.sh @@ -0,0 +1,185 @@ +#!/bin/bash + +# Author: coincashew.eth | coincashew.com +# License: GNU GPL +# https: //github.com/coincashew/ethpillar +# +# Acknowledgments +# validator-install is branched from validator-install written by Accidental-green: https: //github.com/accidental-green/validator-install + +set -u + +# enable command completion +set -o history -o histexpand + +python="python3" + +skip_prompt="" +if [[ $# -eq 1 ]]; then + skip_prompt="$1" +fi + +abort() { + printf "%s\n" "$1" + exit 1 +} + +getc() { + local save_state + save_state=$(/bin/stty -g) + /bin/stty raw -echo + IFS= read -r -n 1 -d '' "$@" + /bin/stty "$save_state" +} + +exit_on_error() { + exit_code=$1 + last_command="${@:2}" + if [ $exit_code -ne 0 ]; then + >&2 echo "\"${last_command}\" command failed with exit code ${exit_code}." + exit $exit_code + fi +} + +wait_for_user() { + local c + echo + echo "Press RETURN to continue or any other key to abort" + getc c + # we test for \r and \n because some stuff does \r instead + if ! [[ "$c" == $'\r' || "$c" == $'\n' ]]; then + exit 1 + fi +} + +shell_join() { + local arg + printf "%s" "$1" + shift + for arg in "$@"; do + printf " " + printf "%s" "${arg// /\ }" + done +} + +# string formatters +if [[ -t 1 ]]; then + tty_escape() { printf "\033[%sm" "$1"; } +else + tty_escape() { :; } +fi +tty_mkbold() { tty_escape "1;$1"; } +tty_underline="$(tty_escape "4;39")" +tty_blue="$(tty_mkbold 34)" +tty_red="$(tty_mkbold 31)" +tty_bold="$(tty_mkbold 39)" +tty_reset="$(tty_escape 0)" + +ohai() { + printf "${tty_blue}==>${tty_bold} %s${tty_reset}\n" "$(shell_join "$@")" +} + +requirements_check() { + # Check CPU architecture + if ! [[ $(lscpu | grep -oE 'x86') || $(lscpu | grep -oE 'aarch64') ]]; then + echo "This machine's CPU architecture is not yet unsuppported." + echo "Recommend using Intel-AMD x86 or arm64 systems for best experience." + exit 1 + fi + + # Check operating system + if ! [[ "$(uname)" == "Linux" ]]; then + echo "This operating system is not yet unsuppported." + echo "Recommend installing Ubuntu Desktop 24.04+ LTS or Ubuntu Server 24.04+ LTS for best experience." + exit 1 + fi +} + +linux_install_pre() { + sudo apt-get update + sudo apt-get install --no-install-recommends --no-install-suggests -y curl git ccze jq tmux bc + exit_on_error $? +} + +linux_install_python() { + which $python + if [[ $? != 0 ]] ; then + ohai "Installing python" + sudo apt-get install --no-install-recommends --no-install-suggests -y $python + else + ohai "Updating python" + sudo apt-get install --only-upgrade -y $python + fi + exit_on_error $? + ohai "Installing python tools" + sudo apt-get install --no-install-recommends --no-install-suggests -y $python-pip $python-tk $python-venv + ohai "Creating venv" + $python -m venv ~/.local --system-site-packages + ohai "Installing pip requirements" + ~/.local/bin/pip install requests console-menu python-dotenv + exit_on_error $? +} + +linux_update_pip() { + PYTHONPATH=$(which $python) + ohai "You are using python@ $PYTHONPATH$" + ohai "Installing python tools" + $python -m pip install --upgrade pip +} + +linux_install_validator-install() { + ohai "Cloning ethpillar into ~/git/ethpillar" + mkdir -p ~/git/ethpillar + git clone https://github.com/coincashew/ethpillar.git ~/git/ethpillar 2> /dev/null || (cd ~/git/ethpillar ; git fetch origin main ; git checkout main ; git pull) + ohai "Installing validator-install" + $python ~/git/ethpillar/deploy-teku-besu.py + ohai "Allowing user to view journalctl logs" + sudo usermod -a -G systemd-journal $USER + ohai "Install complete!" + exit_on_error $? +} + +# Check OS and CPU requirements +requirements_check + +# Do install. +OS="$(uname)" +if [[ "$OS" == "Linux" ]]; then + echo """ +██╗ ██╗ █████╗ ██╗ ██╗██████╗ █████╗ ████████╗ ██████╗ ██████╗ +██║ ██║██╔══██╗██║ ██║██╔══██╗██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ +██║ ██║███████║██║ ██║██║ ██║███████║ ██║ ██║ ██║██████╔╝ +╚██╗ ██╔╝██╔══██║██║ ██║██║ ██║██╔══██║ ██║ ██║ ██║██╔══██╗ + ╚████╔╝ ██║ ██║███████╗██║██████╔╝██║ ██║ ██║ ╚██████╔╝██║ ██║ + ╚═══╝ ╚═╝ ╚═╝╚══════╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ + +██╗███╗ ██╗███████╗████████╗ █████╗ ██╗ ██╗ +██║████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██║ ██║ +██║██╔██╗ ██║███████╗ ██║ ███████║██║ ██║ +██║██║╚██╗██║╚════██║ ██║ ██╔══██║██║ ██║ +██║██║ ╚████║███████║ ██║ ██║ ██║███████╗███████╗ +╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝ + + - Deploy a node, in a flash + - coincashew.com + """ + ohai "This script will install a Teku-Besu Ethereum node:" + echo "git jq curl ccze tmux bc" + echo "python3-tk python3-pip python3-venv" + echo "validator-install" + + if [[ -z $skip_prompt ]]; then wait_for_user; fi + linux_install_pre + linux_install_python + linux_update_pip + linux_install_validator-install + echo "" + echo "" + echo "######################################################################" + echo "## ##" + echo "## VALIDATOR INSTALL COMPLETE To manage, use \"ethpillar\" ##" + echo "## ##" + echo "######################################################################" + echo "" + echo "" +fi \ No newline at end of file diff --git a/manage_validator_keys.sh b/manage_validator_keys.sh index bae9d50..72f1bac 100755 --- a/manage_validator_keys.sh +++ b/manage_validator_keys.sh @@ -8,8 +8,8 @@ # Dir to install staking-deposit-cli STAKING_DEPOSIT_CLI_DIR=$HOME -# Path to validator_keys, contains validator_key folder with keystore*.json files -KEYPATH=$STAKING_DEPOSIT_CLI_DIR/staking-deposit-cli +# Path to deposit cli tool +DEPOSIT_CLI_PATH=$STAKING_DEPOSIT_CLI_DIR/ethstaker_deposit-cli # Initialize variable OFFLINE_MODE=false # Base directory with scripts @@ -23,32 +23,33 @@ source $BASE_DIR/env _platform=$(get_platform) _arch=$(get_arch) -function downloadStakingDepositCLI(){ - if [ -d $STAKING_DEPOSIT_CLI_DIR/staking-deposit-cli ]; then - ohai "staking-deposit-tool already downloaded." +function downloadEthstakerDepositCli(){ + if [ -d $STAKING_DEPOSIT_CLI_DIR/ethstaker_deposit-cli ]; then + ohai "ethstaker_deposit-cli already downloaded." return fi - ohai "Installing staking-deposit-tool" + ohai "Installing ethstaker_deposit-cli" #Install dependencies sudo apt install jq curl -y #Setup variables - RELEASE_URL="https://api.github.com/repos/ethereum/staking-deposit-cli/releases/latest" - BINARIES_URL="$(curl -s $RELEASE_URL | jq -r ".assets[] | select(.name) | .browser_download_url" | grep --ignore-case ${_platform}-${_arch}.tar.gz$)" - BINARY_FILE="staking-deposit-cli.tar.gz" + RELEASE_URL="https://api.github.com/repos/eth-educators/ethstaker-deposit-cli/releases/latest" + #BINARIES_URL="$(curl -s $RELEASE_URL | jq -r ".assets[] | select(.name) | .browser_download_url" | grep --ignore-case ${_platform}-${_arch}.tar.gz$)" + BINARIES_URL="https://github.com/eth-educators/ethstaker-deposit-cli/releases/download/v0.2.1/ethstaker_deposit-cli-66054f5-${_platform}-${_arch}.tar.gz" + BINARY_FILE="ethstaker_deposit-cli.tar.gz" + [[ -z $BINARIES_URL ]] && echo "Error: Unable to determine BINARIES URL" && exit 1 ohai "Downloading URL: $BINARIES_URL" - # Dir to install staking-deposit-cli + # Dir to install ethstaker_deposit-cli cd $STAKING_DEPOSIT_CLI_DIR # Download binary wget -O $BINARY_FILE $BINARIES_URL # Extract archive tar -xzvf $BINARY_FILE -C $STAKING_DEPOSIT_CLI_DIR # Cleanup - rm staking-deposit-cli.tar.gz + rm ethstaker_deposit-cli.tar.gz # Rename - mv staking_deposit-cli*${_arch} staking-deposit-cli - cd staking-deposit-cli + mv ethstaker_deposit-cli*${_arch} ethstaker_deposit-cli } function generateNewValidatorKeys(){ @@ -71,8 +72,8 @@ function generateNewValidatorKeys(){ NUMBER_NEW_KEYS=$(whiptail --title "# of New Keys" --inputbox "How many keys to generate?" 8 78 --ok-button "Submit" 3>&1 1>&2 2>&3) _setKeystorePassword - cd $STAKING_DEPOSIT_CLI_DIR/staking-deposit-cli - KEYFOLDER="${KEYPATH}/$(date +%F-%H%M%S)" + cd $DEPOSIT_CLI_PATH + KEYFOLDER="${DEPOSIT_CLI_PATH}/$(date +%F-%H%M%S)" mkdir -p "$KEYFOLDER" ./deposit --non_interactive new-mnemonic --chain "$NETWORK" --execution_address "$ETHADDRESS" --num_validators "$NUMBER_NEW_KEYS" --keystore_password "$_KEYSTOREPASSWORD" --folder "$KEYFOLDER" if [ $? -eq 0 ]; then @@ -103,9 +104,10 @@ function _getEthAddy(){ function _getNetwork(){ NETWORK=$(whiptail --title "Network" --menu \ - "For which network are you generating validator keys?" 10 78 2 \ + "For which network are you generating validator keys?" 10 78 3 \ "mainnet" "Ethereum - Real ETH. Real staking rewards." \ - "holesky" "Testnet - Practice your staking setup here." \ + "holesky" "long term Testnet - Suitable for staking practice." \ + "ephemery" "short term Testnet - Ideal for staking practice. Monthly resets." \ 3>&1 1>&2 2>&3) } @@ -146,8 +148,8 @@ function addRestoreValidatorKeys(){ whiptail --title "Keystore Password" --msgbox "Reminder to use the same keystore password as existing validators." 10 78 _setKeystorePassword - cd $STAKING_DEPOSIT_CLI_DIR/staking-deposit-cli - KEYFOLDER="${KEYPATH}/$(date +%F-%H%M%S)" + cd $DEPOSIT_CLI_PATH + KEYFOLDER="${DEPOSIT_CLI_PATH}/$(date +%F-%H%M%S)" mkdir -p "$KEYFOLDER" ./deposit --non_interactive existing-mnemonic --chain "$NETWORK" --execution_address "$ETHADDRESS" --folder "$KEYFOLDER" --keystore_password "$_KEYSTOREPASSWORD" --validator_start_index "$START_INDEX" --num_validators "$NUMBER_NEW_KEYS" if [ $? -eq 0 ]; then @@ -167,8 +169,8 @@ function addRestoreValidatorKeys(){ function _setKeystorePassword(){ while true; do # Get keystore password - _KEYSTOREPASSWORD=$(whiptail --title "Keystore Password" --inputbox "Enter your validator's keystore password, must be at least 8 chars. " 12 78 --ok-button "Submit" 3>&1 1>&2 2>&3) - if [[ ${#_KEYSTOREPASSWORD} -ge 8 ]]; then + _KEYSTOREPASSWORD=$(whiptail --title "Keystore Password" --inputbox "Enter your validator's keystore password, must be at least 12 chars. " 12 78 --ok-button "Submit" 3>&1 1>&2 2>&3) + if [[ ${#_KEYSTOREPASSWORD} -ge 12 ]]; then _VERIFY_PASS=$(whiptail --title "Verify Password" --inputbox "Confirm your keystore password" 12 78 --ok-button "Submit" 3>&1 1>&2 2>&3) if [[ "${_KEYSTOREPASSWORD}" = "${_VERIFY_PASS}" ]]; then ohai "Password is same." @@ -198,6 +200,13 @@ function setConfig(){ CSM_WITHDRAWAL_ADDRESS=${CSM_WITHDRAWAL_ADDRESS_HOLESKY} CSM_SENTINEL_URL="https://t.me/CSMSentinelHolesky_bot" ;; + ephemery) + LAUNCHPAD_URL="https://launchpad.ephemery.dev" + LAUNCHPAD_URL_LIDO="https://TBD.testnet.fi/?ref=ethpillar" + CSM_FEE_RECIPIENT_ADDRESS=${CSM_FEE_RECIPIENT_ADDRESS_HOLESKY} + CSM_WITHDRAWAL_ADDRESS=${CSM_WITHDRAWAL_ADDRESS_HOLESKY} + CSM_SENTINEL_URL="https://t.me/CSMSentinelTBD" + ;; esac # Check if Lido CSM Validator @@ -275,7 +284,7 @@ function loadKeys(){ ohai "Starting validator" queryValidatorQueue setLaunchPadMessage - whiptail --title "Next Steps: Upload JSON Deposit Data File" --msgbox "$MSG_LAUNCHPAD" 20 78 + whiptail --title "Next Steps: Upload JSON Deposit Data File" --msgbox "$MSG_LAUNCHPAD" 20 95 whiptail --title "Tips: Things to Know" --msgbox "$MSG_TIPS" 24 78 ohai "Finished loading keys. Press enter to continue." read @@ -285,7 +294,7 @@ function loadKeys(){ function setLaunchPadMessage(){ MSG_LAUNCHPAD="1) Visit the Launchpad: $LAUNCHPAD_URL \n2) Upload your deposit_data-#########.json found in the directory: -\n$KEYFOLDER/validator_keys +\n$KEYFOLDER \n3) Connect the Launchpad with your wallet, review and accept terms. \n4) Complete the 32 ETH deposit transaction(s). One transaction per validator. \n5) Wait for validators to become active. $MSG_VALIDATOR_QUEUE" @@ -293,14 +302,14 @@ function setLaunchPadMessage(){ MSG_TIPS=" - Wait for Node Sync: Before making a deposit, ensure your EL/CL client is synced to avoid missing rewards. \n - Timing of Validator Activation: After depositing, it takes about 15 hours for a validator to be activated unless there's a long entry queue. \n - Backup Keystore Files: Keep copies on offline USB storage. - Location: ~/staking-deposit-cli/$(basename $KEYFOLDER)/validator_keys + Location: $KEYFOLDER \n - Generate Voluntary Exit Message: Once active and assigned an index #, generate your validator's VEM. To stop validator duties, broadcast VEM." MSG_LAUNCHPAD_LIDO="1) Visit Lido CSM: $LAUNCHPAD_URL_LIDO \n2) Connect your wallet on the correct network, review and accept terms. \n3) Copy JSON from your deposit_data-#########.json \nTo view JSON, run command: -cat ~/staking-deposit-cli/$(basename $KEYFOLDER)/validator_keys/deposit*json +cat $KEYFOLDER/deposit* \n4) Provide the ~2 ETH/stETH bond per validator. \n5) Lido will deposit the 32ETH. Wait for your validators to become active. $MSG_VALIDATOR_QUEUE" @@ -308,7 +317,7 @@ cat ~/staking-deposit-cli/$(basename $KEYFOLDER)/validator_keys/deposit*json \n - Wait for Node Sync: Before making the ~2ETH bond deposit, ensure your EL/CL client is synced to avoid missing rewards. \n - Timing of Validator Activation: After depositing, it takes about 15 hours for a validator to be activated unless there's a long entry queue. \n - Backup Keystore Files: Keep copies on offline USB storage. - Location: ~/staking-deposit-cli/$(basename $KEYFOLDER)/validator_keys + Location: $KEYFOLDER \n - Subscribe to CSM Sentinel Bot: Provides your CSM Node Operator events via telegram $CSM_SENTINEL_URL \n - Generate Voluntary Exit Message: Once active and assigned an index #, generate your validator's VEM. To stop validator duties, broadcast VEM." @@ -325,6 +334,8 @@ function queryValidatorQueue(){ declare -A BEACONCHAIN_URLS=() BEACONCHAIN_URLS["mainnet"]="https://beaconcha.in" BEACONCHAIN_URLS["holesky"]="https://holesky.beaconcha.in" + BEACONCHAIN_URLS["ephemery"]="https://beaconchain.ephemery.dev" + # Dencun entry churn cap CHURN_ENTRY_PER_EPOCH=8 EPOCHS_PER_DAY_CONSTANT=225 @@ -367,7 +378,7 @@ function setMessage(){ \nIf you have any questions you can get help at https://dsc.gg/ethstaker" MSG_PATH="Enter the path to your keystore files. \nDirectory contains keystore-m.json file(s). -\nExample: $KEYPATH/YYYY-MM-DD-NNNNNN/validator_keys" +\nExample: $DEPOSIT_CLI_PATH/YYYY-MM-DD-NNNNNN/validator_keys" MSG_ETHADDRESS="Ensure that you have control over this address. \nETH address secured by a hardware wallet is recommended. \nIn checksum format, enter your Withdrawal Address:" @@ -421,5 +432,5 @@ done setWhiptailColors setMessage -downloadStakingDepositCLI +downloadEthstakerDepositCli menuMain diff --git a/resync_consensus.sh b/resync_consensus.sh index 2a1a59e..3b0e2b2 100755 --- a/resync_consensus.sh +++ b/resync_consensus.sh @@ -41,6 +41,9 @@ function getNetwork(){ 11155111) NETWORK="Sepolia" ;; + 39438138) + NETWORK="Ephemery" + ;; esac } @@ -73,6 +76,9 @@ function resyncClient(){ Sepolia) URL="https://sepolia.beaconstate.info" ;; + Ephemery) + URL="https://ephemery.beaconstate.ethstaker.cc" + ;; esac sudo systemctl stop consensus