Running a Local Ethereum Node
If You Node You Node
I’ve experimented with running local nodes before, but the underpowered hardware I was using made that difficult. I recently upgraded everything with various goals in mind, but an important one was the ability to host multiple local nodes for the blockchains I’m interested in (Ethereum, Avalanche, Fantom).
I don’t have enough storage space to run all three at once (yet…) so I prioritized getting the Ethereum node working first.
To my surprise, setting up an Ethereum node is actually quite easy and fast. It also has the lightest technical burden of the three chains I mentioned.
It’s been running for a few days and has a strong connection to the network, so much so that it’s consistently overperforming against Infura (the most popular Ethereum RPC).
There are several clients that you can run. The most popular is Go Ethereum (aka geth) followed by Erigon. I have chosen to run a geth node, largely because of its already massive presence. Everyone is familiar with geth, so I will be, too.
What is a Node?
A node is a full participant in the blockchain network. It connects to other nodes on the peer-to-peer network, transmits and receives new blocks and pending transactions, and optionally attempts to confirm (mine) those transactions and earn block rewards.
Everyone here is familiar with an RPC at this point. We are used to interacting with a blockchain network through the RPC as a trusted middleman.
If you use Metamask without customizing your network list, you will use an Infura RPC endpoint.
Why Run a Node?
There are philosophical and practical reasons to run a node.
Adding a node to the Ethereum network strengthens it. While it is still configured for proof-of-work consensus, each additional node make it harder to attack.
Putting my tinfoil hat on for a moment, how can you truly believe your RPC? There is no practical way to verify the data returned by your RPC is any good. It says my balance is 100 Ether, so my balance must be 100 Ether, right? Maybe.
This very interesting thread suggests that RPC providers can see a lot more than they should.
In the MEV world there are other practical considerations.
For one, running a local node means that I have zero latency when reading state from the blockchain. I have some transaction delay when I submit a transaction outward, but I had that with an RPC anyway.
Also, with a local node I don’t have to deal with request limits. When working with Infura/Moralis/Ankr, I have to take care not to exceed some arbitrary limit (5 request/second, 100 requests/minute, etc). With a local node I can just blast as many calls as I want.
Recall that I’m running Linux on my workstation (specifically Pop!_OS 22.04 LTS), so I have two primary means to install geth:
PPA — A repository maintained by the Ethereum team that provides packages installed via
Docker — A containerized version maintained by the Ethereum team
I have experimented with both and found Docker to be the better choice simply for the ease of starting (or not starting) the node at boot time.
If you don’t have Docker on your system, look up the installation instructions to install Docker Community Edition (CE), which is the fully open sourced version without the commercial licensing.
For Ubuntu installs, it’s quite easy:
sudo apt install docker.io docker-compose
For other distributions, go to the link above and follow the instructions. Or consult the documentation for your distribution’s package manager. There may be a native package for Docker CE that you can install without any extra trouble.
With that done, make sure Docker is running:
devil@hades:~$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2022-07-24 10:05:52 PDT; 1 day 10h ago
TriggeredBy: ● docker.socket
I prefer to put all of my docker container information inside a dedicated
/docker directory, with containers in their own folder.
My geth container is defined in
# - --http.corsdomain=[read below]
This file defines some important options:
--ws and --http enable the HTTP and WebSocket endpoints,
--http.addr enable connection to those endpoints from all other IPs (by default, only localhost is supported). The documentation is vague on this topic, but I believe that the value is a netmask (default 255.255.255.0)
If you are running this on a larger network, consider setting the values more restrictively.
You can also choose to use the IPC feature by attaching it outside the container in a secured location.
Geth does peer-to-peer host discovery on port 30303 (TCP and UDP).
Lighthouse does peer-to-peer host discovery on port 9000 (TCP and UDP).
You may have to forward these ports in your router if you have trouble connecting to peers quickly or maintaining sync.
After you’ve chosen your values, start the container with
sudo docker-compose up -d, then watch the logs using
sudo docker-compose logs -f.
It will begin to synchronize with the network, connect to other peers, download and verify every block from genesis until current.
This will take a day or so to complete, and might complete faster if you have a strong machine and internet connection.
I am fully synced and my data directory consumes roughly 670 GB.
If you need to access the geth console, you can attach to it inside the Docker container using
docker-compose exec [container name] geth attach.
Preparing For The Merge
The Ethereum mainnet transition to proof-of-stake consensus will be activated when the TTD (Total Terminal Difficulty) reaches 58750000000000000000000. At that time, you will need a consensus client running alongside geth to maintain connectivity to the network.
There are several consensus clients available, and I will demonstrate how to connect Lighthouse with Geth here.
Lighthouse publishes a Docker container, which makes adding a consensus client to geth very simple. Lighthouse can be added to the
docker-compose.yml file above very simply, by modifying the compose file as follows:
# - --http.corsdomain=[read below]
Summary of changes:
Create a service for the Lighthouse consensus client, running on port 9000 (both TCP and UDP, forward in your router as needed).
datadirectory (read-only) so it can access the JWT secret (an on-demand generated key for connecting to the RPC auth service).
Enable geth’s authrpc service, authenticated with a JWT secret only readable from the shared file inside geth’s
I have learned a few things from readers needing help on their specific platform. Here are the big ones:
Host networking is not supported on Windows or Mac, so drop that option and forward ports the usual way
Connecting geth and lighthouse is not easily done if both containers are using host networking. I recommend using standard NAT networking, and have edited the docker-compose.yml file above to reflect this configuration.
Docker for Mac has an extra setting that limits the disk space avilable to a container. By default it is too low to hold the geth and lighthouse databases and geth will continuously restart and throw errors. Provide a minimum of 650 GB for geth and 125 GB for lighthouse to ensure your node can bootstrap and run.
This was last updated Oct 1, 2022. The disk space values will increase as the blockchain height increases, so adjust upwards as needed !
Connecting To Metamask
Metamask is an extremely popular web browser plugin for interacting with RPCs. By default, geth will reject HTTP connections from external domains. To use Metamask with your local node, you first need to enable access from one or more domains.
The “easy” (and probably unsafe) choice is to allow access from any domain, which you can enable by adding this to the
command: section above
I prefer to allow access to Metamask only. The domain for a Firefox plugin is
moz-extension://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and the domain for a Chrome plugin is
Chrome plugin IDs are common across installs, so you can use the official extension ID
chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn as described HERE.
Firefox uses unique plugin IDs, so you need to look a little harder. You can find your unique plugin ID for Firefox by pasting
about:debugging#/runtime/this-firefox into your address bar and finding your Metamask section. Copy the “Internal UUID” value and replace the
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx characters above.
You may add multiple domains with a comma separation. Here is an example of adding Metamask domains for both Firefox and Chrome:
Shutdown and Restart
By default, Docker allows a container 10 seconds to terminate after receiving a
SIGTERM signal. If it is still running after 10 seconds, it will send a
SIGKILL signal, which kills all running processes completely.
Geth needs more than 10 seconds to cleanly stop, so if you need to stop or restart the container, I recommend using the
-t option with
docker-compose to allow extra time for it to stop cleanly. Give it a few minutes at least, though an impractically high value (
-t 9999999 for example) will ensure geth has enough time to cleanly stop. This avoids a very long data scrubbing and verification process on the next startup.
With that running, you’ll now to able to access the Ethereum network directly via your local node by pointing your Metamask, Ledger, Trezor, etc. at the HTTP/WS ports.
I’m preparing you by setting up an Ethereum node because the next series of posts will introduce and connect you to FlashBots, then we will start building some Ethereum bots to MEV with the big boys.
Degen Code is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.