Run a node

nulld is the NULL full node. It downloads and verifies the chain, relays transactions, serves a local control socket and, when asked, JSON-RPC over HTTP and Prometheus metrics. This guide takes a node from download to a long-running mainnet service.

Install a release

Releases are published at github.com/NullNetProtocol/null-chain/releases. Each release has one archive per platform and a SHA256SUMS file:

PlatformArchive
Linux x86-64 (glibc 2.28 or later)null-0.1.8-x86_64-unknown-linux-gnu.tar.gz
macOS Apple siliconnull-0.1.8-aarch64-apple-darwin.tar.gz
macOS Intelnull-0.1.8-x86_64-apple-darwin.tar.gz
Windows x86-64null-0.1.8-x86_64-pc-windows-msvc.zip

Every archive holds three binaries: nulld (the node and command line wallet), null-wallet-rpc (the wallet daemon) and null-desktop (the desktop wallet).

Download the archive and SHA256SUMS, check the hash, then install the binaries:

Linux

VERSION=0.1.8
BASE=https://github.com/NullNetProtocol/null-chain/releases/download/v$VERSION
curl -LO $BASE/null-$VERSION-x86_64-unknown-linux-gnu.tar.gz
curl -LO $BASE/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS
tar -xzf null-$VERSION-x86_64-unknown-linux-gnu.tar.gz
sudo install -m 0755 null-$VERSION-x86_64-unknown-linux-gnu/nulld \
  null-$VERSION-x86_64-unknown-linux-gnu/null-wallet-rpc /usr/local/bin/
nulld --version

On macOS use shasum -a 256 --check --ignore-missing SHA256SUMS. On Windows, compare the output of certutil -hashfile null-0.1.8-x86_64-pc-windows-msvc.zip SHA256 with the line in SHA256SUMS.

Binaries are not code-signed. The checksum confirms the download matches the release; for stronger assurance, build from source and compare.

Build from source

You need a Rust toolchain, version 1.85 or later.

git clone https://github.com/NullNetProtocol/null-chain.git
cd null-chain
git checkout v0.1.8
cargo build --release --locked -p null-node
./target/release/nulld --version

This builds nulld and null-wallet-rpc into target/release/. Add -p null-desktop to build the desktop wallet too; on Linux it needs the X11 or Wayland development libraries and an OpenGL-capable driver.

Start a mainnet node

nulld run

With no flags the node joins mainnet, dials the seed nodes seed1 to seed5.nullnet.sh:19000, stores the chain in the default data directory and opens the control socket on 127.0.0.1:18444. The first lines of output say what it serves:

nulld up on the main network
peers      outbound only
control    127.0.0.1:18444
json-rpc   disabled
metrics    disabled
token      /home/you/.local/share/null/main/chain/rpc.token

Stop it with Ctrl-C or SIGTERM; it shuts down cleanly. On Windows, closing the console, logging off and shutting down also stop it cleanly.

Use --datadir to keep the chain somewhere else. Only one process can open a chain database, so stop the desktop wallet before running nulld on its default chain.

Data directory

The node's data directory holds:

FileContents
chain.redbthe chain database
rpc.tokenthe token for the control socket and JSON-RPC, owner-readable only
peers.datthe address book of peers the node verified, kept across restarts

--in-memory keeps the chain in memory instead, for tests and throwaway nodes. Such a node prints its generated token at startup.

Logs

Each line has a time and a level, colored on a terminal (set NO_COLOR or pipe the output to turn colors off). --log-level debug|info|warn|error sets the threshold. --log-file <path> also appends every line to a file, dated in UTC and uncolored, owner-only on Unix. nulld does not rotate this file; use logrotate with copytruncate for a long-running node.

Accept inbound peers

A node connects out only until you give it --listen. To serve peers, listen on the public interface and open TCP port 19000:

nulld run --listen 0.0.0.0:19000

The node announces its public interface address and port to peers automatically. Behind NAT, forward the port and also pass the public endpoint:

nulld run --listen 0.0.0.0:19000 --advertise 203.0.113.10:19000

Other nodes check the endpoint with a full handshake before sharing it, and address exchange repeats every ten minutes, so a reachable listener is discovered without being added to any seed list. Use --connect to dial specific peers and --seed to add bootstrap peers, both repeatable.

Control socket and token

The control socket is a line protocol on 127.0.0.1:18444. A session must start with auth <token>; three refused requests close the connection. The node generates a random token at startup and writes it to rpc.token in the data directory, or uses the one given with --rpc-token or NULL_RPC_TOKEN.

CommandReply
auth <token>ok or err unauthorized
statusok height=<h> hash=<hex> peers=<n> mempool=<n> mined=<n> in_chain=<n> syncing=<bool>
block <height>the block as hex, or err not found
compact <height>the compact block as hex, or err not found
hash <height>the block hash, or err not found
coinbase <height>the coinbase transaction as hex, or err not found
submit <hex tx>ok <txid> or err <reason>
txstatus <txid>ok unknown, ok pooled or ok confirmed <height> <index>
spent <nullifier>ok true or ok false

The wallet commands of nulld (balance, send, consolidate) and null-wallet-rpc talk to the node through this socket. For a quick check:

nulld status --rpc-token-file ~/.local/share/null/main/chain/rpc.token

Enable JSON-RPC

Exchanges, pools and explorers integrate against JSON-RPC 2.0 over HTTP. Turn it on with --rpc-http. It uses the same token as the control socket, sent as a bearer token:

nulld run --rpc-http 127.0.0.1:18446

cURL

curl -s -H "Authorization: Bearer $(cat ~/.local/share/null/main/chain/rpc.token)" \
  -d '{"jsonrpc":"2.0","method":"getblockchaininfo","id":1}' \
  http://127.0.0.1:18446/

Bind it to 127.0.0.1 or a private interface. The endpoint is plain HTTP and the token is the only protection; if another host must reach it, put it behind a TLS proxy or an SSH tunnel, never directly on a public address. To keep the token stable across restarts, pass your own with NULL_RPC_TOKEN instead of reading rpc.token each time. The methods are listed in the JSON-RPC reference.

--blocknotify '<command>' runs a shell command on every new main-chain tip, with %s replaced by the block hash. Pools use it instead of polling; see the pool guide.

Check sync

Three views of the same state:

  • nulld status prints the control socket's status line. syncing=false and a height matching the explorer mean the node is caught up.
  • getblockchaininfo over JSON-RPC returns height, best_block_hash, syncing and the network's upgrade schedule.
  • --metrics 127.0.0.1:9100 serves Prometheus metrics at GET /metrics.
MetricMeaning
null_heightheight of the chain tip
null_peers{direction}connected peers past the handshake, inbound and outbound
null_mempool_transactionstransactions in the mempool
null_blocks_mined{state}blocks this node mined since it started (found), and how many are still in the main chain (in_chain)
null_pending_compact_blockscompact blocks waiting for transactions
null_uptime_secondsseconds since the node started
null_mining_ready1 when the node hands out mining work: caught up and, on mainnet, with at least one peer

The metrics endpoint has no authentication. Bind it to localhost or a monitoring network. Mining itself is covered in Mining.

Run under systemd

Create a system user and install the unit below as /etc/systemd/system/nulld.service. It keeps the chain in /var/lib/null, accepts peers on port 19000 and keeps the control socket and JSON-RPC on localhost.

nulld.service

[Unit]
Description=NULL full node
Wants=network-online.target
After=network-online.target

[Service]
User=null
Group=null
ExecStart=/usr/local/bin/nulld run \
    --datadir /var/lib/null \
    --listen 0.0.0.0:19000 \
    --rpc 127.0.0.1:18444 \
    --rpc-http 127.0.0.1:18446
Restart=on-failure
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=60
StateDirectory=null
StateDirectoryMode=0700
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/null

[Install]
WantedBy=multi-user.target
sudo useradd --system --home-dir /var/lib/null --shell /usr/sbin/nologin null
sudo systemctl daemon-reload
sudo systemctl enable --now nulld
journalctl -u nulld -f
sudo -u null nulld status --rpc-token-file /var/lib/null/rpc.token

Output goes to the journal, so no --log-file is needed. Remove --listen for an outbound-only node, and --rpc-http if nothing needs JSON-RPC.

Run in Docker

The repository has a Dockerfile that builds nulld and runs it as an unprivileged null user with nulld as the entrypoint:

git clone https://github.com/NullNetProtocol/null-chain.git
cd null-chain && git checkout v0.1.8
docker build -t nulld:0.1.8 .
docker run -d --name nulld --restart unless-stopped \
  -v nulld-data:/home/null -p 19000:19000 \
  nulld:0.1.8 run --listen 0.0.0.0:19000

The volume holds the null user's home, so the chain lands in the default data directory, /home/null/.local/share/null/main/chain, and survives container upgrades.

The control socket binds 127.0.0.1 inside the container, so use docker exec to reach it:

docker exec nulld nulld status \
  --rpc-token-file /home/null/.local/share/null/main/chain/rpc.token

Publish JSON-RPC only to the host's loopback: add --rpc-http 0.0.0.0:18446 to the command and -p 127.0.0.1:18446:18446 to docker run.

Tor and I2P

Tor

To send every outbound connection through Tor, run Tor with a SOCKS port and start the node with --proxy:

nulld run --proxy 127.0.0.1:9050 --listen 127.0.0.1:19000

.onion names then work in --connect and --seed. To accept peers as an onion service, add this to torrc:

torrc

SocksPort 127.0.0.1:9050
HiddenServiceDir /var/lib/tor/nulld
HiddenServicePort 19000 127.0.0.1:19000

Share the hostname in /var/lib/tor/nulld/hostname with peers as <hostname>:19000. With a proxy the node does not announce IP addresses, --advertise is rejected, and onion addresses are shared by explicit configuration only.

I2P

Run an I2P router with the SAM bridge enabled and start the node with --i2p:

nulld run --i2p 127.0.0.1:7656 --connect <destination>.i2p:19000

The bare flag --i2p uses the default bridge address 127.0.0.1:7656. The node opens a transient session and reaches .i2p peers passed to --connect or --seed; other peers use the proxy, if any, or a direct connection. Inbound I2P service is not supported yet.

Upgrade

  1. Read the release notes. A release that schedules a network upgrade says so and gives the activation height.
  2. Stop the node (sudo systemctl stop nulld).
  3. Install the new binaries as in Install a release.
  4. Start the node and watch the log.

The chain database records the consensus rules it was built under. If a release changes them, the node refuses to open the old database and names the directory to delete:

the chain in /var/lib/null was built under different consensus rules, for example by an older version; delete that directory and restart to resync

Delete that chain directory and start again; the node resyncs from peers. Wallet files are not affected, because wallets rescan from the node. There is no in-place rewind.