Production Network Deployment Guide
This page illustrates the process of deploying a new production network comprising multiple independent nodes.
Overview
The Tutorial page illustrates the process of creating a new network of nodes by running each one on a single development machine. While that is a quick and concise demonstration of how nodes interact and the validator management process, a real production network requires the deployment of nodes on different host machines that typically operate in remote locations. This page demonstrates the deployment of such a network, with attention to the relevant settings and considerations in such a setting.
Concepts
There are several important concepts to understand when creating a network of independent remote nodes.
Peers and Node IDs
Every Kwil node has a "node ID" that is includes the node's public key and network address. This plays an important role creating secure authenticated connections between nodes over the internet.
A peer is always specified in the format <public key>#<key type>@<IP address>:<port>
. For example, given a node with public
key 02977e1f9bcec8c1b1905cf45cc5cfcfbabf9ca579d42d1b1a228e9b0f1c6adecb
and key type secp256k1
that is reachable at IP address 10.1.2.3
on TCP port 6600
, the notation used to specify this node as a peer is 02977e1f9bcec8c1b1905cf45cc5cfcfbabf9ca579d42d1b1a228e9b0f1c6adecb#[email protected]:6600
.
Unlike node IDs, which are used to secure P2P node communication, Kwil DB user addresses are in their own formats, such as checksummed Ethereum addresses, to support native integration with web3 wallets for database access control and user identification. The set of supported P2P key types is also different from the types of user addresses supported, so an explicit public key format is used for peers.
An operator can determine their node ID in different ways:
- Use the
kwild key info
command, which derives and displays the node ID given the private key. - Use the
kwild admin status
command, which retrieves the public key and its type from the running node's admin RPC service. - Locate the node ID in the node logs. An info-level log entry with the message "NODE: This node is" contains the node identity (public key and key type) and all listening addresses
The node's public key is also displayed when the private key is initially generated with the kwild key gen
subcommand.
Peer Exchange
In a healthy network, nodes have many peers to prevent single points of failure or unintended network partitions, and to support efficient
network gossip. The feature that facilitates this is called peer exchanges (PEX). This is also enabled by default with the p2p.pex = true
setting. Disable this setting only if a node must not share its peers with other nodes.
Network Bootstrapping
The p2p.bootnodes
setting specifies a list of peers to connect to on startup.
This is useful for bootstrapping a new node, especially when used in conjunction
with PEX. To specify two remote peers to always connect to on
startup, list them as follows:
[p2p]
bootnodes = "02ae12856a766c277842e1319fd7e450711b706ef90942e8ad5be9369e5d22817c#[email protected]:6600,02cace485e891cb5e5bd22b008986980791677f9f553fd3bb3f864e6b84e011cdb#[email protected]:6600"
This may also be specified on the command line with the --p2p.bootnodes
option, which may be specified multiple times for each peer rather than a comma-separated list. For example:
kwild start \
--p2p.bootnodes 02ae12856a766c277842e1319fd7e450711b706ef90942e8ad5be9369e5d22817c#[email protected]:6600 \
--p2p.bootnodes 02cace485e891cb5e5bd22b008986980791677f9f553fd3bb3f864e6b84e011cdb#[email protected]:6600
Validators
While not a networking concept, it is important to understand the role of validators as it pertains to block production. The consensus engine used by Kwil requires greater than 1/2 of validators to approve proposed blocks. Thus, if the network is not well connected and validators are unable to communicate efficiently, block production may be slowed or even stopped in the case of a network partition.
Not all nodes in a network must be validators. Non-validator nodes called "sentry" nodes are typically created to function as RPC service providers or just to support network health by relaying transactions, blocks, and misbehavior evidence.
Compatible Versions
A final important concept is version compatibility. On a production network, it is important to only run nodes built from the same release
branch, such as the release-v0.8
branch, or a release tag with the same MAJOR.MINOR
version. Different patch versions are compatible,
but large releases that change the application's state machine logic cannot be run on the same network. For example, it is not possible to
run v0.8.4
and v0.9.1
on the same network. To update a network between such major releases, it is necessary to perform a migration.
Walkthrough
This section illustrates the process for launching a small network of independent remote nodes. It will begin with two validators at genesis, followed by the addition of a sentry node using block sync, and finally the new node will become a validator.
Deploying Genesis Nodes and Validators
Starting a network with a single validator is possible, however, we will demonstrate starting with two validators from genesis since this also illustrates the consensus considerations described above in Validators.
Generating Node Private Keys
Securely generate and store private keys. Anyone in possession of a private key can imitate and act on behalf of a node. In the case of a validator, this impacts the security of the network.
Every node has a unique identity, which is determined by its private key. We will generate a private key using the kwild key gen
command on the node0
host machine:
[node0]:~$ kwild key gen --key-file ~/nodekey.json
This will write a file named nodekey.json
that contains the ed25519 private key for the new node into the user's home directory. Each node must
have a different private key. This command should be run independently by each validator operator.
We can inspect the key using the kwild key info
command as follows:
[node0]:~$ kwild key info --key-file ~/nodekey.json
Key type: secp256k1
Private key (hex): da1ad3ad3e80d200ff66beb6515360337f6e61f14ce958e5bda717fef5aad9f5
Public key (plain hex): 02d3575f74ffcdb17f781f003d42284a4d1adf271558d0417423b8372ec3f34750
The "Public key (plain hex)" and "Key type" value is used in the genesis.json
file to specify the initial validator set as described in the next section.
Creating the Genesis Document
Before starting the first nodes, it is necessary is to create a genesis file for the new network. The file specifies the validator set at genesis,
initial account funding allocations, and other network parameters. All nodes must use the exact same genesis.json
file since its parameters affect
he consensus rules for the network.
Unlike the node quick start guide where all genesis validator keys are generated with the kwild setup testnet
command run once on one machine,
we will create a genesis file with the kwild setup genesis
command. This takes as input:
- the plain hex public keys, key types, and power of each genesis validator (formatted as
name:pubkey:power
) - the "chain ID" string that is the new network's name
- any other default parameter overrides
Consider we want to start a new network called "prod-chain" with two genesis validators:
[organizer]:~$ kwild setup genesis \
--validator "0277fb40d1b745c8f96f411ab57915e2df3bc86efde70f0e687f4273beb6708b82#secp256k1:1" \
--validator "03b0eedfb09ac1e19fa6f5eb1d5ade53c120ef32d988e60bb40ab08133b573ba30#secp256k1:1" \
--leader "0277fb40d1b745c8f96f411ab57915e2df3bc86efde70f0e687f4273beb6708b82#secp256k1" \
--chain-id "prod-chain" --out ~/prod-chain
Created genesis.json file at /home/user/prod-chain/genesis.json
The resulting genesis file has the chain ID and validators customized, with default values for the rest of the document:
{
"chain_id": "prod-chain",
"initial_height": 0,
"validators": [
{
"pubkey": "0277fb40d1b745c8f96f411ab57915e2df3bc86efde70f0e687f4273beb6708b82",
"type": "secp256k1",
"power": 1
},
{
"pubkey": "03b0eedfb09ac1e19fa6f5eb1d5ade53c120ef32d988e60bb40ab08133b573ba30",
"type": "secp256k1",
"power": 1
}
],
"migration": {
"start_height": 0,
"end_height": 0
},
"leader": {
"type": "secp256k1",
"key": "0277fb40d1b745c8f96f411ab57915e2df3bc86efde70f0e687f4273beb6708b82"
},
"db_owner": "0277fb40d1b745c8f96f411ab57915e2df3bc86efde70f0e687f4273beb6708b82",
"max_block_size": 6291456,
"join_expiry": "24h0m0s",
"disabled_gas_costs": true,
"max_votes_per_tx": 200,
"migration_status": "NoActiveMigration"
}
Provide this same genesis.json
file to all node operators. The genesis file with the initial validators will remain the same for the entire lifetime of the network, even as the network adds new validators.
Node Configuration
Unlike genesis.json
, which contains network-wide settings, the config.toml
file is used to configure a Kwil node for its own purpose and network configuration. Use the kwild setup init
command to create the config.toml
file for each node separately, setting the listen addresses, external address, persistent peers, and seeds as appropriate for the node.
The kwild setup init
command initializes a node's "root" directory, which by default is ~/.kwild
. We will use the agreed upon genesis file using the --genesis
flag. Since these are genesis validators, we also use the --key-file
flag to specify the nodekey.json
file that was generated above on each node. Other nodes that are not genesis validators may allow setup init
to generate a new key by omitting this flag.
Consider we have prepared the keys and addresses for the two nodes described above as follows:
IP Address | Node ID | |
---|---|---|
node0 | 23.45.67.89 | 0277fb40d1b745c8f96f411ab57915e2df3bc86efde70f0e687f4273beb6708b82#secp256k1 |
node1 | 98.76.54.32 | 03b0eedfb09ac1e19fa6f5eb1d5ade53c120ef32d988e60bb40ab08133b573ba30#secp256k1 |
To have them connect to each other, we will set their p2p.bootnodes
settings appropriately.
On node0
, we can initialize the root directory and the config.toml
there with the following command, specifying the path to the key file that was generated on this machine:
[node0]:~$ kwild setup init --genesis prod-chain/genesis.json \
--root /data/kwil-node0 --key-file ~/nodekey.json
Kwil node configuration generated successfully at: /data/kwil-node0
On node1
, do the same, but with node0
as a bootnode
[node1]:~$ kwild setup init --genesis prod-chain/genesis.json \
--p2p.bootnodes "0277fb40d1b745c8f96f411ab57915e2df3bc86efde70f0e687f4273beb6708b82#[email protected]:6600" \
--root /data/kwil-node1 --key-file ~/nodekey.json
Kwil node configuration generated successfully at: /data/kwil-node1
Initial Startup and Block Production
Now that both of the validators have been prepared with the same genesis.json
file, and with node1
's boot peer configuration pointing to node0
, it is time to start the nodes.
Depending on how the nodes are deployed, kwild
would be run as a service, container, or in a terminal multiplexer.
PostgreSQL Database
On each node, install PostgreSQL and create a kwild
database. Refer to the Postgres setup guide for more information and quick start instructions.
Start Kwild
Start the nodes on each of the remote machines, specifying the path to the root directory if it is not at the default location (~/.kwild
):
[node0]:~$ kwild start -r /data/kwil-node0
[node1]:~$ kwild start -r /data/kwil-node1
Once the nodes connect, the first block will be produced shortly after:
2025-01-31 17:21:29.992 [INF] CONS: Committed Block {height=1 hash=6dc4f373a39fba30db5c167a328320d735ba62e9e8ca5ef878e31142de50a1eb appHash=2d8f3ceeff2c836527da823d7b654d33d3e44b6159b172235c160001e0c9b4db updates=null}
Recall that both of the validators need to be started and able to communicate before blocks can be created, as discussed in the Validators section. If the nodes are unable to connect, look for peer-related errors in the logs, and check your persistent peers settings and firewall rules.
Adding a New Node
Now that our network of two validators is running and producing blocks, we will create and synchronize a new node to the network, and once it is synchronized, promote the node to a validator.
Starting as a Sentry Node
Now we will add a third note to the network as a sentry node. Since this node is not a genesis validator, its public key is not included
in the genesis file, which is now immutable. The kwild setup init
command can be used to simultaneously initialize a new node's root
directory and generate a new private key.
When adding a node to the network, start it as a non-validator / sentry node. Always completely synchronize the node to the network before attempting to promote it to a validator.
Like before, we will specify the location of the network's genesis.json
file, the host's external IP address, and one or more persistent
peers. However, we will use the generated private key:
[node2]:~$ kwild setup init --genesis prod-chain/genesis.json \
--p2p.bootnodes "0277fb40d1b745c8f96f411ab57915e2df3bc86efde70f0e687f4273beb6708b82#[email protected]:6600" \
--root /data/kwil-node2
Kwil node configuration generated at /data/kwil-node2
We can see the ID of node2
before starting it:
[node2]:~$ kwild key info --key-file /data/kwil-node2/nodekey.json | grep "Node ID"
Node ID: 03dbe22b9922b5c0f8f60c230446feaa1c132a93caa9dae83b5d4fab16c3404a22#secp256k1
Start node2
using the kwild start -r /data/kwil-node2
command.
Verifying Successful Node Synchronization
Before adding the node as a validator, ensure that node2
has completely synchronized with the network. You can do this with either the kwil-admin
tool, or with the public user service using kwil-cli
.
Using kwild admin status
, inspect the .sync.best_block_height
and .sync.syncing
values:
[node2]:~$ kwild admin status
{
"node": {
"chain_id": "kwil-test-chain",
"node_id": "03dbe22b9922b5c0f8f60c230446feaa1c132a93caa9dae83b5d4fab16c3404a22#secp256k1",
"app_ver": 1,
"listen_addr": "127.0.0.1:6600",
"role": "validator",
"rpc_addr": "/tmp/admin.sock"
},
"sync": {
"app_hash": "537935ed434a25f23bfb18324b36ba1936e8be5d0df1a330eab413d848304d96",
"best_block_hash": "9a38b3c78d2a4361c7615db779f8beb073b46819bb275295716283d2c700a6ac",
"best_block_height": 337,
"best_block_time": "2025-01-27T18:45:00.199-06:00",
"syncing": false
},
"validator": {
"pubkey": "03dbe22b9922b5c0f8f60c230446feaa1c132a93caa9dae83b5d4fab16c3404a22",
"type": "secp256k1",
"power": 1
}
}
Ensure that the node is no longer syncing (catching up) with the network and that the best block height is within a couple blocks of the network's known height.
Use kwil-cli utils chain-info
to query the height of any node with its RPC service exposed:
$ kwil-cli utils chain-info --provider "http://98.76.54.32:8484"
Chain ID: prod-chain
Height: 109
Hash: 1d5a4ef85927dce3c90168e89fdfb32219c5c95bb5ed3332eb69e20bbe7346da
Compare the output of that command with the result from node2
to ensure the new node stays in sync with the network as new blocks are produced.
Alternatively, check the new node's health check REST endpoint, which will return an HTTP status code of 200 and a JSON body with "healthy":true
if the node is ready:
[node2]:~$ curl -s --fail http://127.0.0.1:8484/api/v1/health
{"kwil_alive":true,"healthy":true, ...}
Joining the Validator Set
Unlike node0
and node1
, consensus does not require participation from node2
, which just executes the blocks that are produced and approved by the validators. In this section we will add node2
to the validator set.
Start by submitting a validator "join" request from node2
:
[node2]:~$ kwil-admin validators join
TxHash: 0722630c4296ea27d77f4ee8db6129c1ca042372811187f7ba787c75464400ed
We have now submitted a request to become a validator, which the existing validators must approve for this node to join the validator set. The validators list-join-requests
subcommand from any node can view this request:
$ kwil-admin validators list-join-requests
Pending join requests (1 approval needed):
Candidate | Power | Approvals | Expiration
------------------------------------------------------------------------------+-------+-----------+------------
03dbe22b9922b5c0f8f60c230446feaa1c132a93caa9dae83b5d4fab16c3404a22#secp256k1 | 1 | 0 | 14627
Both validators are required to approve this request since 1/2 does not meet the threshold of greater than 2/3 of the validators. This is done by running the following on each validator:
$ kwil-admin validators approve 03dbe22b9922b5c0f8f60c230446feaa1c132a93caa9dae83b5d4fab16c3404a22#secp256k1
TxHash: cab4b66d12cd6ecee256ba73dd9500a3ea7b21ba03bd38d5db7701268a0f8ecb
Once the approval transactions are mined, node2
will become part of the validator set:
$ kwil-admin validators list
Current validator set:
0. {pubkey = 0277fb40d1b745c8f96f411ab57915e2df3bc86efde70f0e687f4273beb6708b82#secp256k1, power = 1}
1. {pubkey = 03b0eedfb09ac1e19fa6f5eb1d5ade53c120ef32d988e60bb40ab08133b573ba30#secp256k1, power = 1}
2. {pubkey = 03dbe22b9922b5c0f8f60c230446feaa1c132a93caa9dae83b5d4fab16c3404a22#secp256k1, power = 1}
The node is now a validator. Keep it running and monitor its health.