DEV Community

Cover image for Step-by-Step Guide to Setting Up Stellar Validator Node
overcat
overcat

Posted on • Updated on

Step-by-Step Guide to Setting Up Stellar Validator Node

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 is GB, and for node C is GC. Their corresponding private keys are SA, SB, and SC. You need to activate these three accounts on the network and set their home domain to example.com.

Let's get started

Setup domain

  • Point core-live-a.example.com, core-live-b.example.com, and core-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, and history.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
Enter fullscreen mode Exit fullscreen mode

Install PosttgreSQL and Stellar Core

sudo apt install postgresql postgresql-contrib stellar-core
Enter fullscreen mode Exit fullscreen mode

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;"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
#!/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"
Enter fullscreen mode Exit fullscreen mode
sudo chmod +x /usr/bin/stellarcp
Enter fullscreen mode Exit fullscreen mode

Configure Stellar Core

sudo -u stellar nano /etc/stellar/stellar-core.cfg
# Add the following configuration
# save the file
Enter fullscreen mode Exit fullscreen mode

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.
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 the node B, replace `Example Node B` config with the node A's info, and so on.
[[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}"
Enter fullscreen mode Exit fullscreen mode

Initialize the database

sudo -u stellar stellar-core --conf /etc/stellar/stellar-core.cfg new-db
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Check the status of Stellar Core:

sudo systemctl status stellar-core
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Setup SSL Certificate

sudo certbot --nginx -d history.core-live-a.example.com
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
# 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/"
Enter fullscreen mode Exit fullscreen mode
sudo chown -R www-data:www-data /var/www/example.com/
sudo chmod -R 755 /var/www/example.com/
Enter fullscreen mode Exit fullscreen mode
sudo nano /etc/nginx/sites-available/example.com
# Add the following configuration
# Save the file
Enter fullscreen mode Exit fullscreen mode
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 *;
    }
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
sudo certbot --nginx -d example.com
Enter fullscreen mode Exit fullscreen mode

Top comments (0)