How to Setup a Fantom (FTM) Validator Node

Christian Lanz
Feb 18, 2020 · 10 min read

The following article is a walk-through on how to set-up a validator node on the Fantom Opera network.

Please note that the requirements below might change in the future as the network processes more transactions and the blockchain gets bigger, so your node may require higher specifications than those listed.

What are we going to do?

  1. Launch Cloud Instance
  2. Setup Non-Root User
  3. Install Required Tools
  4. Register Validator
  5. Run Validator Node
  6. Setup Node Monitoring
  7. Register on Fantom Staker Info

Launch Cloud Instance

You can either run a node on your own hardware or use a cloud provider. I would recommend choosing one of the big cloud providers, e.g. Amazon AWS.

Node Specifications

At least an m5.large General Purpose Instance with 2 CPU and 8GB of memory.

Use Ubuntu Server 18.04 LTS (64-bit) and ensure to allocate enough storage — the Opera mainnet uses ~6.5GB of disk space at the moment. I would recommend going with at least 250GB of disk space for now.

Network Settings

Open up port 22 for SSH, as well as port 5050 for both TCP and UDP traffic.

Setup Non-Root User

If there is already a non-root user available, you can skip this step.

# SSH into your machine
(local)$ ssh root@{VALIDATOR_IP_ADDRESS}
# Update the system
(validator)$ sudo apt-get update && sudo apt-get upgrade -y
# Create a non-root user
(validator)$ USER={USERNAME}
(validator)$ sudo mkdir -p /home/$USER/.ssh
(validator)$ sudo touch /home/$USER/.ssh/authorized_keys
(validator)$ sudo useradd -d /home/$USER $USER
(validator)$ sudo usermod -aG sudo $USER
(validator)$ sudo chown -R $USER:$USER /home/$USER/
(validator)$ sudo chmod 700 /home/$USER/.ssh
(validator)$ sudo chmod 644 /home/$USER/.ssh/authorized_keys

Make sure to paste your public SSH key into the authorized_keys file of the newly created user in order to be able to login via SSH.

# Enable sudo without password for the user
(validator)$ sudo vi /etc/sudoers

Add the following line to the end of the file:

{USERNAME} ALL=NOPASSWD: ALL
/etc/sudoers
# Install zsh
(validator)$ sudo apt-get install -y zsh
# Set zsh as the default shell for the user
(validator)$ sudo vi /etc/passwd

Go to the line with your username (most probably the last one, as you just created the user) and change the default shell at the end of the line from:

/bin/bash --> /bin/zsh
/etc/passwd

Now close the root SSH connection to the machine and login as your newly created user:

# Close the root SSH connection
(validator)$ exit
# Login as new user
(local)$ ssh {USERNAME}@{VALIDATOR_IP_ADDRESS}

Now let’s make the zsh shell a little bit prettier 🙂:

# Install oh-my-zsh
(validator)$ git clone https://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
(validator)$ cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

Exit the shell and login again for the changes be activated:

# Exit the shell
(validator)$ exit
# Re-Login
(local)$ ssh {USERNAME}@{VALIDATOR_IP_ADDRESS}

Install Required Tools

You are still logged in as the new user via SSH. Now we are going to install Go and Lachesis.

First, install the required build tools:

# Install build-essential
(validator)$ sudo apt-get install -y build-essential

Install Go

# Install go
(validator)$ wget https://dl.google.com/go/go1.13.3.linux-amd64.tar.gz
(validator)$ sudo tar -xvf go1.13.3.linux-amd64.tar.gz
(validator)$ sudo mv go /usr/local

Then open up your .zshrc file and export the required Go paths:

# Export go paths
(validator)$ vi ~/.zshrc
# Append at the bottom of .zshrc
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH

Reload your .zshrc config:

# Reload .zshrc config
(validator)$ source ~/.zshrc

Validate your Go installation:

go version

Install Lachesis

# Install Lachesis
(validator)$ mkdir -p $HOME/go/src/github.com/Fantom-foundation
(validator)$ cd $HOME/go/src/github.com/Fantom-foundation/
(validator)$ git clone https://github.com/Fantom-foundation/go-lachesis.git
(validator)$ cd go-lachesis
(validator)$ make build

Validate your Lachesis installation:

./build/lachesis version

Add Lachesis to the path, so you can access it from anywhere:

# Export lachesis path
(validator)$ vi ~/.zshrc
# Append at the bottom of .zshrc
export PATH=$PATH:/home/{USERNAME}/go/src/github.com/Fantom-foundation/go-lachesis/build

Reload your .zshrc config:

# Reload .zshrc config
(validator)$ source ~/.zshrc

You can now access Lachesis from every directory 👌:

# Reload .zshrc config
(validator)$ cd ~
(validator)$ lachesis version

Register Validator

After installing everything we need, you can continue by registering your validator node on-chain. To do this, you need to create a validator wallet. The wallet is the validator’s identity in the network which it uses to authenticate, sign messages, etc.

First, start the Lachesis node to interact with it and to create a validator wallet:

# Start lachesis node
(validator)$ lachesis --nousb

Starting up your node will look something like this:

lachesis — nousb

The node should start to sync the network data right away:

Data sync in action 🔥

Create a validator wallet

The node running and syncing the network in your current console, so you need to open up a new console window, connect via SSH to the server and enter the following commands to create a wallet:

# Create validator wallet
(validator)$ lachesis account new

After entering the command you will get prompted to enter a password for the account (= wallet) — use a strong one! You can e.g. use a password manager to generate a 20+ digit password to secure your wallet.

It will look something like this:

Create validator wallet

⚠️ NEVER share your private key or keystore with anyone!

By the way: The wallet above is not a real wallet we use, it’s just for demonstration purposes only. 😉

Fund your validator wallet

The next step is to fund your validator wallet with enough FTM to become a validator. That means you need to have at least 3,175,000 FTM in the wallet you just created (send a little more to cover transaction fees).

If you don’t have your FTM on the Opera mainnet already, you will need to swap your FTM using the Fantom Bridge:

After successfully swapping FTM to your newly created Opera wallet you can register your validator via the SFC Smart Contract.

Make sure you wait for your node to be fully synced, otherwise your FTM will not show up in your wallet!

Create your validator via the SFC

Open up the console where you entered the commands to create the validator wallet previously and attach to the Lachesis node console:

# Attach to lachesis console
(validator)$ lachesis attach

By doing so, you will get a JavaScript console where you can directly interact with the Lachesis node and e.g. send transactions (which you will do in a moment):

lachesis attach

Now initialize the SFC contract ABI variable:

abi = JSON.parse('[{"constant":true,"inputs":[],"name":"minDelegation","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"bondedRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"stakersNum","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"slashedStakeTotalAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"short","type":"uint256"},{"internalType":"uint256","name":"long","type":"uint256"}],"name":"updateGasPowerAllocationRate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"withdrawDelegation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"minStakeDecrease","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"updateBaseRewardPerSec","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"prepareToWithdrawDelegation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"delegationLockPeriodEpochs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"epochSnapshots","outputs":[{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"epochFee","type":"uint256"},{"internalType":"uint256","name":"totalBaseRewardWeight","type":"uint256"},{"internalType":"uint256","name":"totalTxRewardWeight","type":"uint256"},{"internalType":"uint256","name":"baseRewardPerSecond","type":"uint256"},{"internalType":"uint256","name":"stakeTotalAmount","type":"uint256"},{"internalType":"uint256","name":"delegationsTotalAmount","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maxDelegatedRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"wrID","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"prepareToWithdrawStakePartial","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"contractCommission","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"stakerID","type":"uint256"}],"name":"_upgradeStakerStorage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"maxEpochs","type":"uint256"}],"name":"claimValidatorRewards","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"delegationsTotalAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"updateStakerMetadata","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"minStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"unbondingUnlockPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"increaseDelegation","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"stakeTotalAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"stakeLockPeriodTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"_syncDelegator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"delegationsNum","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"withdrawalRequests","outputs":[{"internalType":"uint256","name":"stakerID","type":"uint256"},{"internalType":"uint256","name":"epoch","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"delegation","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"unbondingStartDate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"stakerID","type":"uint256"},{"internalType":"uint256","name":"epoch","type":"uint256"},{"internalType":"uint256","name":"commission","type":"uint256"}],"name":"_calcValidatorEpochReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"stakeLockPeriodEpochs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"minDelegationIncrease","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getStakerID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"stakerID","type":"uint256"},{"internalType":"uint256","name":"epoch","type":"uint256"}],"name":"_calcRawValidatorEpochReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bondedTargetRewardUnlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"stakerID","type":"uint256"},{"internalType":"uint256","name":"epoch","type":"uint256"},{"internalType":"uint256","name":"delegationAmount","type":"uint256"},{"internalType":"uint256","name":"commission","type":"uint256"}],"name":"_calcDelegationEpochReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"rewardsStash","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"currentEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"maxEpochs","type":"uint256"}],"name":"claimDelegationRewards","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"bondedTargetPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"currentSealedEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"stakersLastID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"rewardsAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unstashRewards","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"dagAdrress","type":"address"},{"internalType":"address","name":"sfcAddress","type":"address"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"createStakeWithAddresses","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"stakerID","type":"uint256"},{"internalType":"uint256","name":"_fromEpoch","type":"uint256"},{"internalType":"uint256","name":"maxEpochs","type":"uint256"}],"name":"calcValidatorRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakerMetadata","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"slashedDelegationsTotalAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"validatorCommission","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"maxStakerMetadataSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bool","name":"isDelegation","type":"bool"},{"internalType":"uint256","name":"stakerID","type":"uint256"}],"name":"_rewardsBurnableOnDeactivation","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"sfcAddress","type":"address"}],"name":"_sfcAddressToStakerID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"uint256","name":"_fromEpoch","type":"uint256"},{"internalType":"uint256","name":"maxEpochs","type":"uint256"}],"name":"calcDelegationRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"e","type":"uint256"},{"internalType":"uint256","name":"v","type":"uint256"}],"name":"epochValidator","outputs":[{"internalType":"uint256","name":"stakeAmount","type":"uint256"},{"internalType":"uint256","name":"delegatedMe","type":"uint256"},{"internalType":"uint256","name":"baseRewardWeight","type":"uint256"},{"internalType":"uint256","name":"txRewardWeight","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawStake","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegations","outputs":[{"internalType":"uint256","name":"createdEpoch","type":"uint256"},{"internalType":"uint256","name":"createdTime","type":"uint256"},{"internalType":"uint256","name":"deactivatedEpoch","type":"uint256"},{"internalType":"uint256","name":"deactivatedTime","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"paidUntilEpoch","type":"uint256"},{"internalType":"uint256","name":"toStakerID","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"to","type":"uint256"}],"name":"createDelegation","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"newSfcAddress","type":"address"}],"name":"updateStakerSfcAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"prepareToWithdrawStake","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"minStakeIncrease","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"minDelegationDecrease","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"createStake","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"bondedTargetStart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"increaseStake","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"wrID","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"prepareToWithdrawDelegationPartial","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"stakerID","type":"uint256"}],"name":"_syncStaker","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"delegationLockPeriodTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"wrID","type":"uint256"}],"name":"partialWithdrawByRequest","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakers","outputs":[{"internalType":"uint256","name":"status","type":"uint256"},{"internalType":"uint256","name":"createdEpoch","type":"uint256"},{"internalType":"uint256","name":"createdTime","type":"uint256"},{"internalType":"uint256","name":"deactivatedEpoch","type":"uint256"},{"internalType":"uint256","name":"deactivatedTime","type":"uint256"},{"internalType":"uint256","name":"stakeAmount","type":"uint256"},{"internalType":"uint256","name":"paidUntilEpoch","type":"uint256"},{"internalType":"uint256","name":"delegatedMe","type":"uint256"},{"internalType":"address","name":"dagAddress","type":"address"},{"internalType":"address","name":"sfcAddress","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":true,"internalType":"address","name":"dagSfcAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CreatedStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":true,"internalType":"address","name":"oldSfcAddress","type":"address"},{"indexed":true,"internalType":"address","name":"newSfcAddress","type":"address"}],"name":"UpdatedStakerSfcAddress","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"}],"name":"UpdatedStakerMetadata","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"diff","type":"uint256"}],"name":"IncreasedStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"uint256","name":"toStakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CreatedDelegation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"diff","type":"uint256"}],"name":"IncreasedDelegation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fromEpoch","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"untilEpoch","type":"uint256"}],"name":"ClaimedDelegationReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fromEpoch","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"untilEpoch","type":"uint256"}],"name":"ClaimedValidatorReward","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"auth","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"rewards","type":"uint256"}],"name":"UnstashedRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isDelegation","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"BurntRewardStash","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"}],"name":"PreparedToWithdrawStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"}],"name":"DeactivatedStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"auth","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"wrID","type":"uint256"},{"indexed":false,"internalType":"bool","name":"delegation","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CreatedWithdrawRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"penalty","type":"uint256"}],"name":"WithdrawnStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"}],"name":"PreparedToWithdrawDelegation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"}],"name":"DeactivatedDelegation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"penalty","type":"uint256"}],"name":"WithdrawnDelegation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"auth","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"wrID","type":"uint256"},{"indexed":false,"internalType":"bool","name":"delegation","type":"bool"},{"indexed":false,"internalType":"uint256","name":"penalty","type":"uint256"}],"name":"PartialWithdrawnByRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"uint256","name":"oldStakerID","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newStakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"UpdatedDelegation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakerID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"delegatedMe","type":"uint256"}],"name":"UpdatedStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"UpdatedBaseRewardPerSec","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"short","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"long","type":"uint256"}],"name":"UpdatedGasPowerAllocationRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}]')

… as well as the SFC contract object itself:

sfc = web3.ftm.contract(abi).at("0xfc00face00000000000000000000000000000000")

After intializing both variables, you can now interact with the network’s SFC. Enter the following command to check that everything works as expected:

# Show info about validator #1
sfc.stakers(1)

If it looks like this, everything is OK (you should not get an error here):

sfc.stakers(1)

Next, try to get your stakerId (= validator id) from the SFC using your previously generated validator wallet address:

# Get your validator id
sfc.getStakerID("{VALIDATOR_WALLET_ADDRESS}")

This should return 0, as you are not registered as a validator yet:

Check validator id

Next, unlock your validator wallet to be able to execute the registration transaction (make sure to use the password you set before):

# Unlock validator wallet
personal.unlockAccount("{VALIDATOR_WALLET_ADDRESS}", "{PASSWORD}", 60)

This will return “true” if unlocking the wallet was successful:

Unlock validator wallet

Next, send the createStake transaction to register your validator (the value is the representation of the smallest FTM unit, so dividing it be 1e18 will result in 3,175,000 FTM):

# Register your validator
tx = sfc.createStake([], {from: "{VALIDATOR_WALLET_ADDRESS}", value: "3175000000000000000000000"})

Make sure to check your registration transaction (could take a few moments to be confirmed):

# Check your registration transaction
ftm.getTransactionReceipt(tx)

Look for the status: “0x1” at the bottom, which means the transaction was successful:

ftm.getTransactionReceipt(tx)

You can also copy the transactionHash and go the Fantom Explorer and check your transaction there:

https://explorer.fantom.network/transactions/…

This would look something like the below:

Transaction in the Fantom Explorer

Finally, execute the following command again to check your stakerId (= validator id):

# Get your validator id
sfc.getStakerID("{VALIDATOR_WALLET_ADDRESS}")

It should now return something other than “0”:

Check validator id

Congrats, you are now a Fantom validator! 🥳

Run Validator Node

Before you run off celebrating, you need to restart your node in validator mode! 😉

Close the Lachesis console window by typing “exit”. Then head back to the console window where you started your node with the following command:

(validator)$ lachesis --nousb

Stop it by pressing CTRL+C.

Run validator node as pm2 process

Install nodejs, npm and the process manager pm2:

(validator)$ sudo apt-get install -y nodejs npm
(validator)$ sudo npm i -g pm2

Next, create a file called “password” in your home directory and input the wallet password, so the validator node has access to it and can unlock the wallet:

# Create password file in home directory
(validator)$ cd ~
(validator)$ touch password
# Put in the password you created earlier
(validator)$ vi password

Next, create a shell script where you put in the start command for the Lachesis node:

# Create script to run the lachesis node
(validator)$ cd ~
(validator)$ touch runNode.sh
(validator)$ chmod +x runNode.sh

Then put the following content into the newly created runNode.sh file:

#!/bin/sh
$HOME/go/src/github.com/Fantom-foundation/go-lachesis/build/lachesis --nousb --validator {VALIDATOR_WALLET_ADDRESS} --unlock {VALIDATOR_WALLET_ADDRESS} --password $HOME/password

Next, let’s create the pm2 config file, where you tell pm2 what application it should run:

# Create pm2 config
(validator)$ touch ecosystem.config.js

Paste the following content into the ecosystem.config.js file:

module.exports = { apps : [ { name: "fantom", script: "/home/{USERNAME}/runNode.sh", exec_mode: "fork", exec_interpreter: "bash"} ] }

Then start the Lachesis node using pm2:

# Start lachesis node with pm2
(validator)$ pm2 start ./ecosystem.config.js

This will start-up the Lachesis node as a pm2 controlled process. You can check the status via:

(validator)$ pm2 status
pm2 status

You can also attach to the process to see the logs:

(validator)$ pm2 logs
pm2 logs

Finally tell pm2 to autostart the Lachesis node upon system restart:

(validator)$ pm2 startup
(validator)$ pm2 save
pm2 startup
pm2 save

Now your node is running as a pm2 process and will restart in case the system reboots. 👍

Setup Node Monitoring

After successfully setting up and and running your node, the last thing you can do is set up services to monitor your node.

A tool I can definitely recommend is updown.io (this link will get you 100k free credits, so you can easily get started)

updown.io

You can configure updown.io to perform regular health checks for your node and receive immediate notification if something goes wrong:

Admin dashboard

To create a health check for your node, you can create a TCP check on port 5050:

Create a new health check

You can set the check to be performed at various intervals, I would set it to the fastest — 15 seconds.

I also recommend to set up Telegram notifications, so updown.io will immediately send you a notification as soon as your node experiences any kind of issues and you can fix them as fast as possible.

In the end, you get a nice dashboard you can share with your community to keep track of your node’s uptime:

Public dashboard

Register on Fantom Staker Info

Finally (now for real 😄), I want to encourage you to also register your validator node on Fantom Staker Info. That’s a dapp we — block42 — created for the Fantom community in order to browse information about the network and its validators.

https://fantomstaker.info

You can find a guide on how to register your node on our GitHub (it only takes a few minutes 😉):

Cheers on you setting up a validator node! Excited to see that more and more people are joining Fantom. 👊

Photo by Wil Stewart on Unsplash

Support

I hope this guide helped you setting up your Fantom validator node. Feel free to get in contact with me/us if you have any questions, etc.:

block42

A venture-driven tech company.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store