Step-by-Step Guide to Setting Up Stellar Validator Node
overcat
Posted on June 12, 2024
Note:
- The tutorial will guide you to set up three validation nodes.
- The tutorial is brief and should help you set up a node within 15 minutes (excluding time for synchronization).
- I will not explain in detail the reasons for each step here, please refer to the detailed documentation provided by SDF for more information.
- You need to have a basic understanding of Stellar.
- You need to have a basic understanding of Linux.
- Some community members requested a simpler setup guide, so I wrote this tutorial hoping it helps. If you have any questions, feel free to ask in the Stellar Dev Discord.
Prerequisites:
- Three servers with Ubuntu 22.04 installed. You can find the hardware requirements here.
- A domain name; we assume your domain is
example.com
. - Three Stellar accounts for the nodes. We assume the account for node A is
GA
, for node B isGB
, and for node C isGC
. Their corresponding private keys areSA
,SB
, andSC
. You need to activate these three accounts on the network and set their home domain toexample.com
.
Let's get started
Setup domain
- Point
core-live-a.example.com
,core-live-b.example.com
, andcore-live-c.example.com
to the IP addresses of nodes A, B, and C, respectively. - Point
history.core-live-a.example.com
,history.core-live-b.example.com
, andhistory.core-live-c.example.com
to the IP addresses of nodes A, B, and C, respectively. - Point
example.com
to any server; it can be the IP address of node A, B, or C, or it could be another server. Here, we assume it points to the IP address of node A.
Setup Stellar Core
Add the Stellar repository
sudo curl -fsSL https://apt.stellar.org/SDF.asc -o /etc/apt/keyrings/SDF.asc
sudo chmod a+r /etc/apt/keyrings/SDF.asc
echo "deb [signed-by=/etc/apt/keyrings/SDF.asc] https://apt.stellar.org $(lsb_release -cs) stable" | sudo tee -a /etc/apt/sources.list.d/SDF.list
sudo apt update
Install PosttgreSQL and Stellar Core
sudo apt install postgresql postgresql-contrib stellar-core
Setup PostgreSQL
Let's create a new database user and database.
sudo -u postgres psql -c "CREATE ROLE stellar WITH LOGIN;"
sudo -u postgres psql -c "CREATE DATABASE stellar OWNER stellar;"
Add a custom cp command
This is to allow Nginx
to have permission to read these files.
sudo nano /usr/bin/stellarcp
# Add the following content
# Save the file
#!/bin/bash
# Check the number of arguments
if [ "$#" -ne 2 ]; then
echo "Usage: stellarcp <source_file> <destination_file>"
exit 1
fi
# Copy the file
cp "$1" "$2"
# modify the permissions of the destination file
chmod 0644 "$2"
sudo chmod +x /usr/bin/stellarcp
Configure Stellar Core
sudo -u stellar nano /etc/stellar/stellar-core.cfg
# Add the following configuration
# save the file
You need to talk care of the EDIT ME
in the configuration file. In addition, you can use https://stellarbeat.io to search for existing nodes on the network in order to select suitable validator nodes to add to your own configuration file.
In the example configuration file, the three nodes of lightsail.network
are set by me. You can keep or remove them according to your preference.
⚙️ click to display stellar-core.cfg
# complete example config: https://github.com/stellar/stellar-core/blob/master/docs/stellar-core_example.cfg
# Path to the file you want stellar-core to write its log to.
# You can set to "" for no log file.
LOG_FILE_PATH="/var/log/stellar/stellar-core.log"
# BUCKET_DIR_PATH (string) default "buckets"
# Specifies the directory where stellar-core should store the bucket list.
# This will get written to a lot and will grow as the size of the ledger grows.
BUCKET_DIR_PATH="/var/lib/stellar/buckets"
# Sets the DB connection string for SOCI.
# DATABASE="postgresql://user=stellar password=passw&rd host=127.0.0.1 port=5432 dbname=stellar"
# Local Peer connection string
DATABASE="postgresql://dbname=stellar user=stellar host=/var/run/postgresql/"
# This example also adds a common name to NODE_NAMES list named `self` with the
# public key associated to this seed
# EDIT ME: replace the NODE_SEED with your own seed
# EDIT ME: for the node A, use `SA`, for node B, use `SB`, and so on.
NODE_SEED="SA self"
# HOME_DOMAIN for this validator
# Required when NODE_IS_VALIDATOR=true
# When set, this validator will be grouped with other validators with the
# same HOME_DOMAIN (as defined in VALIDATORS/HOME_DOMAINS)
# EDIT ME: replace `example.com` with your domain
NODE_HOME_DOMAIN="example.com"
# Only nodes that want to participate in SCP should set NODE_IS_VALIDATOR=true.
# Most instances should operate in observer mode with NODE_IS_VALIDATOR=false.
# See QUORUM_SET below.
NODE_IS_VALIDATOR=true
# CATCHUP_COMPLETE (true or false) defaults to false
# if true will catchup to the network "completely" (replaying all history)
# if false will look for CATCHUP_RECENT for catchup settings
# If you set it to false, then you should be able to complete the synchronization in a short time.
# NOTE: Even if you want to synchronize the full history, I do not recommend setting CATCHUP_COMPLETE to true.
# Instead, you should use the [stellar-archivist](https://github.com/stellar/go/tree/master/tools/stellar-archivist) tool to download the full historical data.
CATCHUP_COMPLETE=false
# DEPRECATED_SQL_LEDGER_STATE (bool) default false
# When set to true, SQL is used to store all ledger state instead of
# BucketListDB. This is not recommended and may cause performance degregradation.
# This is deprecated and will be removed in the future. Note that offers table
# is still maintained in SQL when this is set to false, but all other ledger
# state tables are dropped.
DEPRECATED_SQL_LEDGER_STATE=false
# HTTP_PORT (integer) default 11626
# What port stellar-core listens for commands on.
# If set to 0, disable HTTP interface entirely
HTTP_PORT=11626
# PUBLIC_HTTP_PORT (true or false) default false
# If false you only accept stellar commands from localhost.
# Do not set to true and expose the port to the open internet. This will allow
# random people to run stellar commands on your server. (such as `stop`)
PUBLIC_HTTP_PORT=false
# WORKER_THREADS (integer) default 11
# Number of threads available for doing long durations jobs, like bucket
# merging and vertification.
WORKER_THREADS=11
# MAX_CONCURRENT_SUBPROCESSES (integer) default 16
# History catchup can potentially spawn a bunch of sub-processes.
# This limits the number that will be active at a time.
MAX_CONCURRENT_SUBPROCESSES=16
# Configure which network this instance should talk to
NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015"
# COMMANDS (list of strings) default is empty
# List of commands to run on startup.
# Right now only setting log levels really makes sense.
COMMANDS=["ll?level=info"]
############################
# list of HOME_DOMAINS
############################
[[HOME_DOMAINS]]
HOME_DOMAIN = "www.stellar.org"
QUALITY = "HIGH"
[[HOME_DOMAINS]]
HOME_DOMAIN = "stellar.blockdaemon.com"
QUALITY = "HIGH"
[[HOME_DOMAINS]]
HOME_DOMAIN = "publicnode.org"
QUALITY = "HIGH"
[[HOME_DOMAINS]]
HOME_DOMAIN = "satoshipay.io"
QUALITY = "HIGH"
[[HOME_DOMAINS]]
HOME_DOMAIN = "lobstr.co"
QUALITY = "HIGH"
[[HOME_DOMAINS]]
HOME_DOMAIN = "lightsail.network"
QUALITY = "HIGH"
# EDIT ME: edit the following to match your domain
[[HOME_DOMAINS]]
HOME_DOMAIN = "example.com"
QUALITY = "HIGH"
# EDIT ME: I suggest you choose a variety of validators
# EDIT ME: to increase the decentralization of the network.
# See https://developers.stellar.org/network/core-node/admin-guide/configuring#choosing-your-quorum-set
############################
# List of Validators
############################
[[VALIDATORS]]
NAME = "SDF 1"
PUBLIC_KEY = "GCGB2S2KGYARPVIA37HYZXVRM2YZUEXA6S33ZU5BUDC6THSB62LZSTYH"
ADDRESS = "core-live-a.stellar.org:11625"
HISTORY = "curl -sf http://history.stellar.org/prd/core-live/core_live_001/{0} -o {1}"
HOME_DOMAIN = "www.stellar.org"
[[VALIDATORS]]
NAME = "SDF 2"
PUBLIC_KEY = "GCM6QMP3DLRPTAZW2UZPCPX2LF3SXWXKPMP3GKFZBDSF3QZGV2G5QSTK"
ADDRESS = "core-live-b.stellar.org:11625"
HISTORY = "curl -sf http://history.stellar.org/prd/core-live/core_live_002/{0} -o {1}"
HOME_DOMAIN = "www.stellar.org"
[[VALIDATORS]]
NAME = "SDF 3"
PUBLIC_KEY = "GABMKJM6I25XI4K7U6XWMULOUQIQ27BCTMLS6BYYSOWKTBUXVRJSXHYQ"
ADDRESS = "core-live-c.stellar.org:11625"
HISTORY = "curl -sf http://history.stellar.org/prd/core-live/core_live_003/{0} -o {1}"
HOME_DOMAIN = "www.stellar.org"
[[VALIDATORS]]
NAME = "Blockdaemon Validator 1"
PUBLIC_KEY = "GAAV2GCVFLNN522ORUYFV33E76VPC22E72S75AQ6MBR5V45Z5DWVPWEU"
ADDRESS = "stellar-full-validator1.bdnodes.net:11625"
HISTORY = "curl -sf https://stellar-full-history1.bdnodes.net/{0} -o {1}"
HOME_DOMAIN = "stellar.blockdaemon.com"
[[VALIDATORS]]
NAME = "Blockdaemon Validator 2"
PUBLIC_KEY = "GAVXB7SBJRYHSG6KSQHY74N7JAFRL4PFVZCNWW2ARI6ZEKNBJSMSKW7C"
ADDRESS = "stellar-full-validator2.bdnodes.net:11625"
HISTORY = "curl -sf https://stellar-full-history2.bdnodes.net/{0} -o {1}"
HOME_DOMAIN = "stellar.blockdaemon.com"
[[VALIDATORS]]
NAME = "Blockdaemon Validator 3"
PUBLIC_KEY = "GAYXZ4PZ7P6QOX7EBHPIZXNWY4KCOBYWJCA4WKWRKC7XIUS3UJPT6EZ4"
ADDRESS = "stellar-full-validator3.bdnodes.net:11625"
HISTORY = "curl -sf https://stellar-full-history3.bdnodes.net/{0} -o {1}"
HOME_DOMAIN = "stellar.blockdaemon.com"
[[VALIDATORS]]
NAME = "Hercules by OG Technologies"
PUBLIC_KEY = "GBLJNN3AVZZPG2FYAYTYQKECNWTQYYUUY2KVFN2OUKZKBULXIXBZ4FCT"
ADDRESS = "hercules.publicnode.org:11625"
HISTORY = "curl -sf https://hercules-history.publicnode.org/{0} -o {1}"
HOME_DOMAIN = "publicnode.org"
[[VALIDATORS]]
NAME = "Lyra by BP Ventures"
PUBLIC_KEY = "GCIXVKNFPKWVMKJKVK2V4NK7D4TC6W3BUMXSIJ365QUAXWBRPPJXIR2Z"
ADDRESS = "lyra.publicnode.org:11625"
HISTORY = "curl -sf https://lyra-history.publicnode.org/{0} -o {1}"
HOME_DOMAIN = "publicnode.org"
[[VALIDATORS]]
NAME = "Boötes"
PUBLIC_KEY = "GCVJ4Z6TI6Z2SOGENSPXDQ2U4RKH3CNQKYUHNSSPYFPNWTLGS6EBH7I2"
ADDRESS = "bootes.publicnode.org:11625"
HISTORY = "curl -sf https://bootes-history.publicnode.org/{0} -o {1}"
HOME_DOMAIN = "publicnode.org"
[[VALIDATORS]]
NAME = "SatoshiPay Iowa"
PUBLIC_KEY = "GAK6Z5UVGUVSEK6PEOCAYJISTT5EJBB34PN3NOLEQG2SUKXRVV2F6HZY"
ADDRESS = "stellar-us-iowa.satoshipay.io:11625"
HISTORY = "curl -sf https://stellar-history-us-iowa.satoshipay.io/{0} -o {1}"
HOME_DOMAIN = "satoshipay.io"
[[VALIDATORS]]
NAME = "SatoshiPay Singapore"
PUBLIC_KEY = "GBJQUIXUO4XSNPAUT6ODLZUJRV2NPXYASKUBY4G5MYP3M47PCVI55MNT"
ADDRESS = "stellar-sg-sin.satoshipay.io:11625"
HISTORY = "curl -sf https://stellar-history-sg-sin.satoshipay.io/{0} -o {1}"
HOME_DOMAIN = "satoshipay.io"
[[VALIDATORS]]
NAME = "SatoshiPay Frankfurt"
PUBLIC_KEY = "GC5SXLNAM3C4NMGK2PXK4R34B5GNZ47FYQ24ZIBFDFOCU6D4KBN4POAE"
ADDRESS = "stellar-de-fra.satoshipay.io:11625"
HISTORY = "curl -sf https://stellar-history-de-fra.satoshipay.io/{0} -o {1}"
HOME_DOMAIN = "satoshipay.io"
[[VALIDATORS]]
NAME = "LOBSTR 1 (Europe)"
PUBLIC_KEY = "GCFONE23AB7Y6C5YZOMKUKGETPIAJA4QOYLS5VNS4JHBGKRZCPYHDLW7"
ADDRESS = "v1.stellar.lobstr.co:11625"
HISTORY = "curl -sf https://archive.v1.stellar.lobstr.co/{0} -o {1}"
HOME_DOMAIN = "lobstr.co"
[[VALIDATORS]]
NAME = "LOBSTR 2 (Europe)"
PUBLIC_KEY = "GCB2VSADESRV2DDTIVTFLBDI562K6KE3KMKILBHUHUWFXCUBHGQDI7VL"
ADDRESS = "v2.stellar.lobstr.co:11625"
HISTORY = "curl -sf https://archive.v2.stellar.lobstr.co/{0} -o {1}"
HOME_DOMAIN = "lobstr.co"
[[VALIDATORS]]
NAME = "LOBSTR 3 (North America)"
PUBLIC_KEY = "GD5QWEVV4GZZTQP46BRXV5CUMMMLP4JTGFD7FWYJJWRL54CELY6JGQ63"
ADDRESS = "v3.stellar.lobstr.co:11625"
HISTORY = "curl -sf https://archive.v3.stellar.lobstr.co/{0} -o {1}"
HOME_DOMAIN = "lobstr.co"
[[VALIDATORS]]
NAME = "LOBSTR 4 (Asia)"
PUBLIC_KEY = "GA7TEPCBDQKI7JQLQ34ZURRMK44DVYCIGVXQQWNSWAEQR6KB4FMCBT7J"
ADDRESS = "v4.stellar.lobstr.co:11625"
HISTORY = "curl -sf https://archive.v4.stellar.lobstr.co/{0} -o {1}"
HOME_DOMAIN = "lobstr.co"
[[VALIDATORS]]
NAME = "LOBSTR 5 (India)"
PUBLIC_KEY = "GA5STBMV6QDXFDGD62MEHLLHZTPDI77U3PFOD2SELU5RJDHQWBR5NNK7"
ADDRESS = "v5.stellar.lobstr.co:11625"
HISTORY = "curl -sf https://archive.v5.stellar.lobstr.co/{0} -o {1}"
HOME_DOMAIN = "lobstr.co"
[[VALIDATORS]]
NAME = "Lightsail Network 1"
PUBLIC_KEY = "GCAT2DUDAW7FSNEX4O2TKH3X2UN6RM6LJGBS4FVV6UN62ROBPWYDMYY5"
ADDRESS = "core-live-a.lightsail.network:11625"
HISTORY = "curl -sf https://core-live-a-history.lightsail.network/{0} -o {1}"
HOME_DOMAIN = "lightsail.network"
[[VALIDATORS]]
NAME = "Lightsail Network 2"
PUBLIC_KEY = "GBZSLUW7NHPXCJN7SIDUGDH754VFYDPXZII6S74EGV6I5Y34BHE7E2PJ"
ADDRESS = "core-live-b.lightsail.network:11625"
HISTORY = "curl -sf https://core-live-b-history.lightsail.network/{0} -o {1}"
HOME_DOMAIN = "lightsail.network"
[[VALIDATORS]]
NAME = "Lightsail Network 3"
PUBLIC_KEY = "GA3FLRTZLNMBXCQ2GG4W2CO2WXWGDDROCD3KVD5QYMYB5NXBUYMO2QXT"
ADDRESS = "core-live-c.lightsail.network:11625"
HISTORY = "curl -sf https://core-live-c-history.lightsail.network/{0} -o {1}"
HOME_DOMAIN = "lightsail.network"
# EDIT ME: replace the following with your own validators, this is for the node A.
# EDIT ME: For node A, you need to set the information of nodes B and C below.
# EDIT ME: For node B, you need to set the information of nodes A and C below.
# EDIT ME: For node C, you need to set the information of nodes A and B below.
[[VALIDATORS]]
NAME = "Example Node B"
PUBLIC_KEY = "GB"
ADDRESS = "core-live-b.example.com:11625"
HISTORY = "curl -sf https://history.core-live-b.example.com/{0} -o {1}"
HOME_DOMAIN = "example.com"
[[VALIDATORS]]
NAME = "Example Node C"
PUBLIC_KEY = "GC"
ADDRESS = "core-live-c.example.com:11625"
HISTORY = "curl -sf https://history.core-live-c.example.com/{0} -o {1}"
HOME_DOMAIN = "example.com"
# HISTORY
# Used to specify where to fetch and store the history archives.
# Fetching and storing history is kept as general as possible.
# Any place you can save and load static files from should be usable by the
# stellar-core history system. s3, the file system, http, etc
# stellar-core will call any external process you specify and will pass it the
# name of the file to save or load.
# Simply use template parameters `{0}` and `{1}` in place of the files being transmitted or retrieved.
# You can specify multiple places to store and fetch from. stellar-core will
# use multiple fetching locations as backup in case there is a failure fetching from one.
#
# Note: any archive you *put* to you must run `$ stellar-core new-hist <historyarchive>`
# once before you start.
# for example this config you would run: $ stellar-core new-hist local
# this creates a `local` archive on the local drive
# NB: this is an example, in general you should probably not do this as
# archives grow indefinitely
[HISTORY.local]
get="cp /var/lib/stellar/history/{0} {1}"
put="stellarcp {0} /var/lib/stellar/history/{1}"
mkdir="mkdir -p /var/lib/stellar/history/{0}"
Initialize the database
sudo -u stellar stellar-core --conf /etc/stellar/stellar-core.cfg new-db
If the configuration is correct, you will not see any error message.
Initialize the history archive
sudo -u stellar stellar-core --conf /etc/stellar/stellar-core.cfg new-hist local
If you see cp: target '/var/lib/stellar/history/.well-known/stellar-history.json' is not a directory
, don't worry, it's not your fault. You can ignore this message.
Start Stellar Core
sudo systemctl restart stellar-core
Check the status of Stellar Core:
sudo systemctl status stellar-core
You should see Active: active (running)
. If you see other messages, please check the log file /var/log/stellar/stellar-core.log
.
Check the sync status
stellar-core --conf /etc/stellar/stellar-core.cfg http-command 'info'
You should see the state
is Catching up
, now, you need to wait until the state
is Synced!
. Because we have set CATCHUP_COMPLETE
to false
, the validator will not synchronize the complete historical records. You should be able to complete the synchronization in a few minutes or hours.
Publishing History Archives
Next, let's publish the history archives. We'll use Nginx to publish the locally stored history archives. You can use other methods to publish the history archives, such as publicly exposing the AWS S3 bucket.
The following example publishes history archives on node A. For nodes B and C, you need to repeat this process, adjusting the configuration file EDIT ME
sections and replacing the domain information appropriately.
Install Nginx and Certbot
sudo apt install nginx certbot python3-certbot-nginx
Setup Nginx
# Create a new site configuration file, replace `history.core-live-a.example.com` with your domain
sudo nano /etc/nginx/sites-available/history.core-live-a.example.com
# Add the following configuration
# Save the file
server {
listen 80;
root /var/lib/stellar/history/;
# EDIT ME: replace `history.core-live-a.example.com` with your domain
server_name history.core-live-a.example.com;
# do not cache 404 errors
error_page 404 /404.html;
location = /404.html {
add_header Cache-Control "no-cache" always;
}
# do not cache history state file
location ~ ^/.well-known/stellar-history.json$ {
add_header Cache-Control "no-cache" always;
try_files $uri =404;
}
# cache entire history archive for 1 day
location / {
add_header Cache-Control "max-age=86400";
try_files $uri =404;
}
}
Enable the site
sudo ln -s /etc/nginx/sites-available/history.core-live-a.example.com /etc/nginx/sites-enabled/
# Test the configuration
sudo nginx -t
# Reload Nginx
sudo nginx -s reload
Setup SSL Certificate
sudo certbot --nginx -d history.core-live-a.example.com
Setup Stellar TOML
Here we will set up a .well-known/stellar.toml
file to publish node information. Earlier, we pointed example.com
to the IP address of node A, so we will set up this file on node A.
sudo mkdir -p /var/www/example.com/.well-known/
sudo nano /var/www/example.com/.well-known/stellar.toml
# Add the following content
# Save the file
# See https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md for more information.
VERSION = "2.0.0"
NETWORK_PASSPHRASE = "Public Global Stellar Network ; September 2015"
[DOCUMENTATION]
ORG_NAME = "Example"
ORG_URL = "https://example.com"
[[VALIDATORS]]
ALIAS="ena"
DISPLAY_NAME="Example Node A"
HOST="core-live-a.example.com:11625"
PUBLIC_KEY="GA"
HISTORY="https://history.core-live-a.example.com/"
[[VALIDATORS]]
ALIAS="enb"
DISPLAY_NAME="Example Node B"
HOST="core-live-b.example.com:11625"
PUBLIC_KEY="GA"
HISTORY="https://history.core-live-b.example.com/"
[[VALIDATORS]]
ALIAS="enc"
DISPLAY_NAME="Example Node C"
HOST="core-live-c.example.com:11625"
PUBLIC_KEY="GC"
HISTORY="https://history.core-live-c.example.com/"
sudo chown -R www-data:www-data /var/www/example.com/
sudo chmod -R 755 /var/www/example.com/
sudo nano /etc/nginx/sites-available/example.com
# Add the following configuration
# Save the file
server {
listen 80;
# EDIT ME: replace `example.com` with your domain
server_name example.com;
root /var/www/example.com/;
location / {
try_files $uri $uri/ =404;
}
location /.well-known/stellar.toml {
add_header Content-Type text/plain;
add_header Access-Control-Allow-Origin *;
}
}
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
# Test the configuration
sudo nginx -t
# Reload Nginx
sudo nginx -s reload
sudo certbot --nginx -d example.com
Posted on June 12, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.