Skip to main content

Full Node Installation

note

These instructions are for testnet. Mainnet instructions will be made available shortly.

Please refer to the Hardware Requirements before continuing.

Components

  • monad-bft - Consensus client
  • monad-execution - Execution client
  • monad-rpc - RPC client
  • monad-mpt - One-time execution for initializing the disk for TrieDB making the disk available for it. Not required when upgrading unless explicitly mentioned

Prerequisites

  • Ubuntu 24.04+ OS and Kernel >= 6.8 (see warning below)
  • Disable hyperthreading or simultaneous multithreading (SMT) via BIOS settings - these can degrade performance
warning

There is a known bug affecting Linux kernel versions v6.8.0.56-generic - v6.8.0.59-generic (inclusive) that causes Monad clients to hang in an uninterruptible sleep state, severely impacting node stability. We recommend v6.8.0.60-generic or higher.


Prepare the Node

info

Instructions below assume you are running from root user

Provision TrieDB Disk

Set the drive to be used for TrieDB, create a new partition table and a partition that spans the entire drive (the drive should be on a disk that has no filesystem mounted and no RAID configured).

TRIEDB_DRIVE=/dev/nvme1n1 # CHANGE THIS TO YOUR NVME DRIVE
parted $TRIEDB_DRIVE mklabel gpt
parted $TRIEDB_DRIVE mkpart triedb 0% 100%

Create a udev rule to set permissions and create a symlink for the partition.

PARTUUID=$(lsblk -o PARTUUID $TRIEDB_DRIVE | tail -n 1)
echo "ENV{ID_PART_ENTRY_UUID}==\"$PARTUUID\", MODE=\"0666\", SYMLINK+=\"triedb\"" | tee /etc/udev/rules.d/99-triedb.rules

Trigger and reload udev rules.

udevadm trigger
udevadm control --reload

Give permissions to the /dev/triedb drive

chmod a+rwx /dev/triedb

Check LBA Configuration

Enable 512 byte LBA if not enabled.

Install nvme-cli which is used to check for 512 byte LBA.

apt-get install nvme-cli

Check if 512 byte LBA is enabled on TRIEDB_DRIVE.

nvme id-ns -H $TRIEDB_DRIVE | grep 'LBA Format' | grep 'in use'

This command should return the following expected output.

LBA Format 0 : Metadata Size: 0 bytes - Data Size: 512 bytes - Relative Performance: 0 Best (in use)
info

Data Size should be set to 512 bytes and marked as (in use). If that is not the case, then you will need to set the TRIEDB_DRIVE to use 512 byte LBA with the following command.

nvme format --lbaf=0 $TRIEDB_DRIVE

Verify that the configuration has been corrected.

nvme id-ns -H $TRIEDB_DRIVE | grep 'LBA Format' | grep 'in use'

Create Dedicated User

Create a non-privileged user named monad with a home directory and Bash shell.

useradd -m -s /bin/bash monad

Create config directories in /home/monad.

sudo -u monad bash -c "
mkdir -p /home/monad/monad-bft/config \
/home/monad/monad-bft/ledger \
/home/monad/monad-bft/config/forkpoint \
/home/monad/monad-bft/config/validators
"
note
  • /home/monad/monad-bft/config/node.toml - contains configurable consensus parameters, most notably the name of the node, typically "<PROVIDER_NAME>-1" and a list of upstream validators (denoted by secp pubkey and DNS) who have been configured to republish blocks to your full node.
  • /home/monad/monad-bft/config/forkpoint/ - contains consensus quorum checkpoints (written every block) used for bootstrapping state
  • /home/monad/monad-bft/config/validators - contains validator sets generated at the boundary block. The newly generated file contains the consensus validator sets for the current epoch and for the upcoming epoch. The most recent validator set is found at validators.toml.
  • /home/monad/monad-bft/ledger - contains consensus (BFT) block headers and bodies, including the transactions

Add APT Repo

cat <<EOF > /etc/apt/sources.list.d/category-labs.sources
Types: deb
URIs: https://pkg.category.xyz/
Suites: noble
Components: main
Signed-By: /etc/apt/keyrings/category-labs.gpg
EOF

Add GPG Key

curl -fsSL https://pkg.category.xyz/keys/public-key.asc | gpg --dearmor -o /etc/apt/keyrings/category-labs.gpg

Install Monad

apt update
apt install monad=0.12.0~rc

Configure UFW

Configure UFW to allow SSH inbound connections (remote access) and traffic to port 8000. The Monad consensus client uses both TCP and UDP.

# allow ssh connections
sudo ufw allow ssh
# allow p2p port
sudo ufw allow 8000
# enable ufw
# default behavior: block all incoming traffic and enable all outgoing
sudo ufw enable
Hardware firewalls

If using hardware firewalls, you may need to perform additional steps to open up port 8000 to UDP and TCP traffic.


Add Configuration Files

# Create backup folder for BLS and SECP keys
mkdir -p /opt/monad/backup
# Make monad owner of the backup directory
chown -R monad:monad /opt/monad/backup
info

Instructions below assume you are running from monad user

MF_BUCKET=https://bucket.monadinfra.com
curl -o /home/monad/.env $MF_BUCKET/config/testnet/latest/.env.example
curl -o /home/monad/monad-bft/config/node.toml $MF_BUCKET/config/testnet/latest/full-node-node.toml

Update /home/monad/.env

In /home/monad/.env, type a strong password in KEYSTORE_PASSWORD. This password should be wrapped in single quotes, e.g. KEYSTORE_PASSWORD='str0ngp@ssw0rd' and is used for key encryption / decryption.


Key Management

Generate encrypted BLS and SECP keys using the keystore binary:

# source the .env file which will load the KEYSTORE_PASSWORD
source /home/monad/.env
# Create the SECP key
monad-keystore create \
--key-type secp \
--keystore-path /home/monad/monad-bft/config/id-secp \
--password "${KEYSTORE_PASSWORD}" > /opt/monad/backup/secp-backup
# Create the BLS key
monad-keystore create \
--key-type bls \
--keystore-path /home/monad/monad-bft/config/id-bls \
--password "${KEYSTORE_PASSWORD}" > /opt/monad/backup/bls-backup

Keypairs will be generated and backups stored locally under secp-backup and bls-backup. Please make sure keys are properly backed up.

For convenience, save pubkeys in /home/monad:

# Create a backup of both keys in /home/monad
grep "public key" /opt/monad/backup/secp-backup /opt/monad/backup/bls-backup > /home/monad/pubkey-secp-bls

Sign Name Record

After the keypairs are created, a fullnode will need to sign its name record using the SECP key in order to participate in peer discovery.

source /home/monad/.env
monad-sign-name-record \
--address $(curl -s4 ifconfig.me):8000 \
--keystore-path /home/monad/monad-bft/config/id-secp \
--password "${KEYSTORE_PASSWORD}" \
--self-record-seq-num 0

This should provide an output in the form as shown below.

self_address = "<IP>:8000"
self_record_seq_num = 0
self_name_record_sig = "<HEX SIG>"

The output will need to be used when filling in the node.toml later.


Launch the Monad Stack

Full node configurations

Full nodes can receive block proposals either directly from a validator that whitelists it, or via a raptorcast group. These different configurations are described here.

The instructions displayed here are intended for the public full node configuration, where a full node joins the network by connecting to available upstream validators participating in secondary raptorcast. Joining in this configuration is permissionless.

Update node.toml

Update the node.toml that was downloaded previously (for reference, you can also see it here):

  1. Update the node_name field in node.toml to include your provider name and remove '#' from the variable. Note that if you are provisioning multiple nodes, this name should be unique. We recommend a naming scheme like full_<PROVIDER>-1, full_<PROVIDER>-2, etc.

    node_name = # "full_<PROVIDER>-<OPTIONAL_SUFFIX>"
  2. Update the peer_discovery section with the IP addresses and name record signature obtained from Sign Name Record.

  3. Update the beneficiary field to include the address that should receive block rewards (in the event your node will ever be a staked validator). If that's not the case, this can be set to the burn address "0x0000000000000000000000000000000000000000" or any other valid address. This address should be prefixed by 0x.

  4. Ensure enable_client = true under [fullnode_raptorcast].

  5. [blocksync_override] and [statesync] peers can be left empty.

node.toml (snippet)
[[bootstrap.peers]]
address = "bootstrap-node-ip:8000"
secp256k1_pubkey = "<bootstrap node pubkey>"
record_seq_num = <name record sequence number>
name_record_sig = "<bootstrap node name record signature>"
[blocksync_override]
peers = []
[statesync]
peers = []
[fullnode_raptorcast]
enable_publisher = true
enable_client = true
raptor10_fullnode_redundancy_factor = 3.0
max_group_size = 150
round_span = 240
invite_lookahead = 120
max_invite_wait = 10
deadline_round_dist = 10
init_empty_round_span = 23
max_num_group = 3
invite_future_dist_min = 1
invite_future_dist_max = 600
invite_accept_heartbeat_ms = 10000
Directory structure check

The final directory structure should look similar to this:

/home/monad
└── .env
└── monad-bft/
└──config
├── id-bls
├── id-secp
└── node.toml
└──validators
└── validators.toml
└──forkpoint
└── forkpoint.toml

monad-cruft

Installation of the monad Debian package enables the monad-cruft timer, which runs hourly to clear old artifacts (/opt/monad/scripts/clear-old-artifacts.sh). This is necessary to prevent inode exhaustion as artifacts like forkpoint.toml and ledger files accumulate.

Initialize the database

systemctl start set-hugepages
systemctl start monad-mpt

Check journalctl to ensure that this worked correctly.

Below is an example of a successful outcome:

$ journalctl -u monad-mpt
# NOTE: output is "trimmed" for easier reading here
MPT database on storages:
Capacity Used % Path
1.82 Tb 1.85 Gb 0.10% "/dev/nvme1n1p1"
MPT database internal lists:
Fast: 7 chunks with capacity 1.75 Gb used 1.60 Gb
Slow: 1 chunks with capacity 256.00 Mb used 0.00 bytes
Free: 7441 chunks with capacity 1.82 Tb used 0.00 bytes
MPT database has 281868 history, earliest is 0 latest is 281867.
It has been configured to retain no more than 33554432.
Latest voted is (281866, 281868).
Latest finalized is 281865, latest verified is 281862, auto expire version >

Hard Reset

Follow the Hard Reset instructions, including the commands to bring the stack up (systemctl start ...).

View available CLI arguments

Execute the CLI --help command via the desired binary. Please note that these should not be changed arbitrarily as some configurations may result in unexpected behavior or crashes.

monad-rpc --help

Install & Start OTEL Collector

curl -fsSL -o /tmp/otelcol_0.125.0_linux_amd64.deb https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.125.0/otelcol_0.125.0_linux_amd64.deb
dpkg -i /tmp/otelcol_0.125.0_linux_amd64.deb
cp /opt/monad/scripts/otel-config.yaml /etc/otelcol/config.yaml
systemctl start otelcol

The Debian package supports OTEL collector. Through this, you will be able to see all the relevant Monad-specific metrics (available at 0.0.0.0:8889/metrics).