# Run a node

> Run a NeuroChain node — build from source (contracts first), start a single test node or a multi-validator network, Docker Compose, data layout, keys, snapshots, monitoring with Prometheus and Grafana, backup and restore.

Source: https://docs.nro.world/operate/node

## Requirements

- Rust (stable) with the `wasm32-unknown-unknown` target
- A C++ toolchain and `clang` (RocksDB is built from source)
- Node.js 18+ for the gateway
- A few GB of disk for the build

## Build

The node embeds two system contracts, so build them **first**:

```bash
cd neurochain
rustup target add wasm32-unknown-unknown

for c in nrc20 ncert; do
  (cd contracts/$c && RUSTFLAGS="-C link-arg=--allow-undefined" \
     cargo build --release --target wasm32-unknown-unknown)
done

cargo build -p neurochain-node --bin neurochain        # → target/debug/neurochain
```

Without the contract step the node build fails with `couldn't read …/nrc20.wasm`. The `--allow-undefined` flag lets the contracts' host imports link with current Rust; new contracts can declare `#[link(wasm_import_module = "env")]` instead (see [NeuroWASM](/neurowasm/#a-minimal-contract)).

The first build takes several minutes (RocksDB); later builds take seconds. Use `--release` for anything beyond local testing.

## A single local node

```bash
CHAIN_ENV=testnet \
NEUROCHAIN_DATA_DIR=./data \
NEUROCHAIN_MODELS_DIR=./models \
NEUROCHAIN_P2P_PORT=30333 \
NEUROCHAIN_RPC_PORT=9933 \
NEUROCHAIN_NODE_ADDR=localhost:30333 \
NEUROCHAIN_MIN_VALIDATORS=1 \
RUST_LOG=neurochain=info \
./target/debug/neurochain
```

- `CHAIN_ENV=testnet` turns on the faucet and relaxes signature checks for development. Leave it unset (mainnet rules) for anything real.
- Without `NEUROCHAIN_GENESIS_VALIDATORS` the node makes itself the only validator (`neuro:node-1.node`) and starts producing blocks at once.
- `NEUROCHAIN_MIN_VALIDATORS=1` silences the warning about having fewer than 7 validators.

Check it:

```bash
curl -s http://localhost:9933/health
# {"status":"ok","height":6,"validator_count":1,"bft_quorum":1,"bft_ready":false,…}
```

Then start the [gateway](./gateway).

## A multi-validator network

Every validator must start from the **same genesis**, which lists every validator with its consensus public key. The repository automates this for a local network:

```bash
cd neurochain
scripts/net-test/net-test.sh keys 4    # create 4 validator keys, write the shared genesis set
scripts/net-test/net-test.sh up 4      # start them (node 1 first, the rest bootstrap from it)
scripts/net-test/net-test.sh check     # heights agree, blocks are produced, sponsored gas is consistent
scripts/net-test/net-test.sh down
```

It uses RPC ports 9941+, P2P ports 30341+ and data under `.net-test/`, so it does not touch a normal `./data` node.

To do it by hand:

1. **Create each validator's key.** Start each node once with its own `NEUROCHAIN_DATA_DIR`, `NEUROCHAIN_NODE_ADDR` and ports. It writes its key to `<data dir>/shard0/bft_key.bin` and logs:
   ```
   Consensus validator … — ML-DSA-65 pubkey (put in NEUROCHAIN_GENESIS_VALIDATORS): 6e80bab8…
   ```
   Stop it and delete `<data dir>/shard0/state_rdb` and `<data dir>/shard0/wal`. **Keep `bft_key.bin`.**
2. **Write the genesis set** — the same value for every node:
   ```
   NEUROCHAIN_GENESIS_VALIDATORS='neuro:val1.node|host1:30333|1000000|0.9|<pubkey1>,neuro:val2.node|host2:30333|1000000|0.9|<pubkey2>,…'
   ```
   Fields: validator address, its P2P address (must equal that node's `NEUROCHAIN_NODE_ADDR`), stake in NRO, initial trust (reported only), ML-DSA-65 public key. An entry without a public key is left out of consensus.
3. **Start** the first node, then the others with `NEUROCHAIN_BOOTSTRAP=host1:30333`. Set `NEUROCHAIN_MIN_VALIDATORS` to your validator count if it is below 7.

Consensus messages travel over the same P2P port; there is no separate consensus port. A node recognises which validator it is by matching `NEUROCHAIN_NODE_ADDR` with the P2P address in the genesis set.

::: tip The genesis set never changes
Validators that join later register a `.node` and stake (see [Staking](/learn/staking)); they do not edit `NEUROCHAIN_GENESIS_VALIDATORS`. A node started with a different genesis set or balance refuses the others' blocks and logs `STATE DIVERGENCE`.
:::

## Docker Compose

From the repository root:

```bash
cp .env.example .env        # edit as needed
docker compose up -d        # node, gateway, web app
docker compose --profile monitoring up -d   # plus Prometheus and Grafana
```

The images build the contracts and the node in stages. Node data lives in the `node1-data` volume; AI model files are mounted read-only from `neurochain/models`.

## Data directory

| Path | Content |
|------|---------|
| `<data dir>/shard<N>/bft_key.bin` | The node's consensus and signing key — **back it up, never share it** |
| `<data dir>/shard<N>/state_rdb/` | RocksDB chain state of shard N |
| `<data dir>/shard<N>/wal/` | Consensus write-ahead log, replayed after a restart |

## Starting from a snapshot

A new node can skip replaying history by loading a signed state snapshot from a running node:

```bash
NEUROCHAIN_CHECKPOINT_URL=http://trusted-node:9933 ./target/debug/neurochain
```

On an empty data directory the node fetches `/api/v1/state/snapshot`, applies it, and catches up from that height. A snapshot reveals every balance and stake; protect the endpoint with `NEUROCHAIN_SNAPSHOT_TOKEN` (clients then send `Authorization: Bearer <token>`).

## Exposure and TLS

- The RPC listens on `localhost` under mainnet rules and on `0.0.0.0` under `CHAIN_ENV=testnet`; set `NEUROCHAIN_RPC_HOST` to choose.
- Set `NEUROCHAIN_TLS_CERT` and `NEUROCHAIN_TLS_KEY` (PEM paths) to serve the RPC over HTTPS.
- Put the gateway, not the node, in front of users.
- `NEUROCHAIN_INTERNAL_TOKEN` protects internal endpoints (oracle log writes, AI data export) with an `X-Internal-Token` header.

## Monitoring

`GET /metrics` serves Prometheus metrics:

| Metric | Meaning |
|--------|---------|
| `nc_block_height`, `nc_bft_round` | Chain progress |
| `nc_bft_validators_active`, `nc_bft_quorum_size` | Validator set |
| `nc_peer_count`, `nc_mempool_size`, `nc_tx_total` | Network and load |
| `nc_total_staked`, `nc_base_fee`, `nc_fees_burned` | Economics (amounts in uNRO) |
| `nc_deploy_rejected_total{rule_id}` | Deploys refused by VMGuardian, per rule |
| `nc_model_active_count`, `nc_model_voting_count`, `nc_model_version{…}`, `nc_model_confirms_needed`, `nc_model_voting_deadline_remaining_blocks` | AI model registry |
| `nc_sentinel_flagged_total{category}`, `nc_sentinel_held_total`, `nc_sentinel_released_total{how}` | Sentinel |

`monitoring/` holds the Prometheus scrape config and a provisioned Grafana dashboard, with alerts for a large mempool, fewer than 2 peers and a burst of rejected deploys.

## Backup and restore

```bash
./scripts/backup.sh [data_dir] [backup_dir]     # live copy; the node keeps running
./scripts/restore.sh <backup_dir> [data_dir]    # stop the node first
```

Backups include `bft_key.bin` — store them as securely as the key itself.

## Logs worth knowing

| Log line | Meaning |
|----------|---------|
| `consensus decided, committing height=…` | A block was finalised |
| `Below minimum: n/7 active validators` | Fewer validators than `NEUROCHAIN_MIN_VALIDATORS` |
| `STATE DIVERGENCE` | This node's state differs from the proposer's; it refuses to vote until resolved |
| `genesis validator(s) missing ML-DSA pubkey` | An entry in the genesis set has no public key and is excluded |

All variables: [Environment](./env).
