today i go over how to set a goerli validator with geth and prysm, both locally or using (docker) containerization.
the code in this post is available here.
all right, anon, if you are serious about being a blockchain leet h4xoor developoor buildoor, you gotta run your own node at some point in your life. this way, you will able to understand the many pieces of the ethereum network. oh, and, yeah, running just a light node is kinda cheating, just sayin’.
besides the learnings, full control and sovereignty, helping the community, etc. etc., the other cool aspect of running your own testnet node validator is that you will learn real life hands-on skills (e.g., issues on deployments or transaction broadcast, CPU spikes, memory leaks, inconsistent peering, etc.).
plus, if you ever decide to stake and run your own mainnet validator (and receive that nice passive income while helping to save the world), you should try out setting a testnet first (and perhaps even a few permutations of the consensus and execution clients).
since the Merge (i.e., when the beacon chain was merged to the main chain decoupling the ethereum network and migrating from proof-of-work to proof-of-stake), a node is now composed of an execution-layer and a consensus-layer working together.
therefore, ethereum nodes communicate peer-to-peer to secure the network, and require both execution and consensus client software.
execution nodes use execution client software to process transactions and smart contracts in ethereum's execution layer.
here are the options:
geth (golang)
erigon (golang)
nethermind (.net)
besu (java)
akula (rust)
the beacon chain was launched by separating the consensus layer from the execution client.
a beacon node will talk to other beacon nodes via peer-to-peer networking, to a local execution node, and (optionally) to a local validator.
validators are responsible for proposing and attesting blocks, fully replacing proof-of-work miners with proof-of-stake collateral stakers.
here are the options for beacon / validator clients:
prysm (golang)
lighthouse (rust)
teku (java)
nimbus (nim, python)
lodestar (typescript)
in this post, we will be installing geth as our execution client and prysm as our beacon/validator client.
these are the most popular options, and although client diversity is important for the robustness and security of the network, i will leave the exploration of client permutations as an exercise for you, anon.
Let’s start, shall we?
to run your node you will need:
a relatively modern multi-core CPU
8GB RAM (16GB is better, especially for mainnet)
an SSD (look for NVMe) of at least 1TB (2TB is recommended for mainnet, and 12TB+ if you want an archive node)
a stable internet connection with good download speed and monthly data allowance
previously, you would need 32 (goerli) ETH to run a validator (just like in the mainnet). however, goerli testnet validator deposits were recently updated to only require 0.0001 (goerli) ETH. this sweet fake money can be mined from this faucet.
here are some architectural choices:
i will show you how to run your node locally with a fast external SSD hard drive. however, you could also run it in the cloud.
i will show you two options: 1) how to run directly from your terminal, or 2) running it with docker and docker-compose. containerization is more upfront work but more peace of mind later on. if this is your first time, i suggest you follow this tutorial. if you are confident, i suggest you set everything with docker following my github repo.
i will leave it as an exercise setting up your node in kubernetes, which could be done using the docker setup above. here is some code to get you started.
as a validator, to be able to stake your ETH, you need to create deposit keys. these are a JSON way to interchange BLS12-381 private keys defined by EIP-2335, and consisting of:
a withdrawal key that controls the funds on the validators, i.e., when you deposit or withdraw them. this key can be saved somewhere else, such as in a cold storage.
a signing key that is responsible for signing the actions that your validator performs on the network every epoch. this key needs to be always available.
let’s create these keys by running staking deposit cli. install it following README.md
and then generate keys from a new mnemonic:
./deposit.sh new-mnemonic --num_validators=1
--mnemonic_language=english
the 🐘 means success:
it also means that you now have a directory called validator_keys
that contains:
a keystore.json
file per validator, containing the signing key and encrypted by your password.
a deposit_data.json
file with the public key associated with the validator, used to stake your (goerli) ETH
move this directory to somewhere safe and, of course, keep your mnemonic safe.
your node will have two independent clients running, and they need to talk to each other and the network securely through an authenticated port.
this is done by the JSON web token (JWT) authentication scheme, represented by a file that contains randomly generated 32-byte hex strings.
there are several ways to create this secret, and some consensus clients have built-in commands for that. i use the good ol’ openssl
and move it to a safe accessible directory:
openssl rand -hex 32 | sudo tee jwt.hex > /dev/null
before you start installing things, especially if you are running your node in a local machine as opposed to the cloud, you need to answer some questions:
where will you install the clients’ executables?
where will keep the execution data?
where will you keep the beacon/node data?
where will you keep the JWT keys that need continuous access?
where will you keep the deposit (signing and withdrawal) keys?
these are my suggestions if you are running your node in a local machine, without containerization:
ethereum
where you keep your source code and projects (say ~/src
), with the structure below. from there, you can install other clients in the future or customize any code later on:📂 ethereum
┣ 📂 consensus
┣ 📂 prysm
(install prism client here)
┣ 📂 execution
┣ 📂 geth
(install geth client here if you are building from source)
geth.ipc
by creating a symlink to the hard drive.
📂 keys
📂 ethereum
┣ 📂 consensus
┣ 📂 execution
do you see 📂 keys
there? move your JWT key and your signing key into this dir (they need to be accessible at all times). you may also leave your withdrawal key there as well, just remember to protect this HD with your life 💀.
finally, if you are running locally, you want to have 3 terminal windows for each: execution client, beacon, and validator. i recommend setting this up with tmux. later on, you will want to either move to docker or set these services to run in the background (via launchd or systemd, etc.).
follow the official documentation to install geth in your machine.
then choose how you are going to customize your execution client. there are several options to choose from and, ideally, you want to set them into a TOML
config file (like this one).
you need to choose how you will serve your node and what APIs and customization you will enable. this is one of the simplest setups you can run (in your first terminal window):
geth --goerli --http --http.api eth,net,engine,admin,web3 \
--http.vhosts="*" --http.corsdomain "*" \
--authrpc.jwtsecret /Volumes/<ssd name>/keys/jwt.hex \
--datadir=/Volumes/<ssd name>/ethereum/execution/
the flag --http
enables the HTTP-RPC server and the flag --http.api
enables the APIs offered over the HTTP-RPC interface.
after full node synchronization (which can take a few days), this is how your execution client should look like:
inside your src/consensus
directory, install prysm with:
mkdir prysm && cd prysm
curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh && chmod +x prysm.sh
now, once again, you need to make choices on how you will run your beacon client.
first, since you are also running a validator, you should set a wallet address for suggested-fee-recipient
(where you will earn validator fees).
but beyond that, there are several options to choose from and ideally you want to set them into a config file. but for this local setup, this is one of the simplest ways to start your beacon node (in your second terminal window), which will connect to the execution node on port 8551
:
./prysm.sh beacon-chain --prater \
--execution-endpoint=http://localhost:8551 \
--jwt-secret=/Volumes/<ssd name>/keys/jwt.hex \
--suggested-fee-recipient=<your wallet address> \
--db-backup-output-dir=/<ssd name>/ethereum/consensus \
--datadir=/<ssd name>/ethereum/consensus
later on, you will want to set your beacon configs into a YAML
file (like this one), and run this instead:
./prysm.sh beacon-chain --config-file=</path/to/file.yaml>
remember from step 0: you need (goerli) ETH to become validator.
grab the deposit JSON key you generated on step 1 and upload at ethereum’s launchpad.
it should take up to a day for your validator to become green and start validating things (once the chain is fully synchronized, of course).
remember that in the post-merge era, validators have two responsibilities:
propose blocks (or produce - and wait for that whole PBS thing).
attest (or vote on) the validity of blocks that are produced.
so let’s start our validator!
first, we need to link it to the deposit (validator) keys we generated earlier:
./prysm.sh validator accounts import \
--keys-dir=/Volumes/<ssd name>/keys
then we can start with:
./prysm.sh validator --goerli \
--wallet-dir=/Volumes/<ssd name>/keys/wallet \
--suggested-fee-recipient=<your wallet address>
--graffiti="<some cool message that does not identify ya>"
later on, you will want to set your validator to a YAML
file (like this one) and run this instead:
./prysm.sh validator --config-file=</path/to/file.yaml>
your validator won’t be ready until your beacon node is fully synced. this can take up to a few days.
you can check the sync status by curl-ing localhost
at port 3500
:
curl http://localhost:3500/eth/v1/node/syncing
you can leave your execution client, beacon node, and validator client terminal windows open and running. when you see "is_syncing":false
, the beacon node is fully synchronized with the ethereum blockchain, and it will automatically begin proposing and validating blocks.
at this point, the validator is assigned to a shard either as a proposer or attester, with a ticker that works every slot (6 seconds).
your goerli validator has been set. monitor its status, income, effectiveness, etc. at beaconcha.in (just use your deposit public key inside the deposit_data-*.json
file as the input string).
to establish connection, both clients send and receive messages through specific ports.
i recommend blocking inbound traffic across all local ports, allowing inbound traffic on a port-by-port basis:
geth → keep 30303 TCP/UDP open to supporting other execution nodes.
prysm → keep 13000 TCP, 12000 UDP open.
clients connection → if not using IPC, the beacon connects to its execution through port 8551.
if you moving your validator client to another machine, don’t forget to migrate your keys and your slashing protection history:
./prysm.sh validator slashing-protection-history export \
--datadir=</path/to/validatorDb> \
--slashing-protection-export-dir=</path/to/desired/outputdir>
if you lose your slashing protection history, wait for a few epochs before starting your validator.
this was a nice intro to get you started. once you understand the basics, you should try the following:
benchmark your node, earnings, and how long it takes to propose a successful block.
make you node discoverable and run JSON-RPC
calls.
run everything on docker, docker-compose, and possibly kubernetes.
try different combinations of consensus and execution clients (and contribute to their open-source code).
explore mev-boost (or wait for my next post!).
set up monitoring, alerts, deployment in the cloud with terraform, and all that.
find wally here.