I’m looking at ways to improve the sentinel / pillar setup where a user runs their own hardware / firewall and has more control over their network topology. Specifically, I would like to make the Discovery
and StaticNodes
variables configurable in the config.json
file. The goal is to allow a user to setup a sentinel and pillar where the pillar sits in a VPC and it only connects to the sentinel for syncing. That way a pillar can be 100% isolated from the internet. Today, the Sentrify setup from Moon simply blocks access to all endpoints, but for an approved endpoint, at the local firewall level. This method works, but I suspect it would be more efficient to take advantage of the StaticNodes
variable.
With the help of Cursor, I’ve laid out the next steps to implement this. @georgezgeorgez @vilkris do these steps look appropriate and are there any downstream issues you see with implementing this?
Making Discovery and StaticNodes Configurable via config.json
Currently, the discovery
and staticNodes
settings in the Zenon node are hardcoded and not configurable through config.json
. This post explains the changes needed to make these settings configurable.
Current Implementation
Currently, these settings are hardcoded in node/node.go
:
node.server = &p2p.Server{
// ...
Discovery: true, // Always enabled
StaticNodes: nil, // Always empty
// ...
}
The codebase already has robust node parsing functionality in p2p/discover/node.go
that can parse node URLs in the format:
enode://<node-id>@<ip>:<port>
This is demonstrated by the existing Nodes()
method in p2p/config.go
which parses seeders:
func (c *Net) Nodes() ([]*discover.Node, error) {
var err error
nodes := make([]*discover.Node, len(c.Seeders))
for index, nodeAddress := range c.Seeders {
nodes[index], err = discover.ParseNode(nodeAddress)
if err != nil {
return nil, err
}
}
return nodes, nil
}
Required Changes
1. Update the NetConfig Structure
First, we need to add the new fields to the NetConfig
structure in node/config.go
:
type NetConfig struct {
ListenHost string
ListenPort int
MinPeers int
MinConnectedPeers int
MaxPeers int
MaxPendingPeers int
Discovery bool // Add this field
StaticNodes []string // Add this field
Seeders []string
}
2. Update the Server Configuration
Modify the makeNetConfig
function in node/config.go
to include the new fields:
func (c *Config) makeNetConfig() *p2p.Net {
networkDataDir := filepath.Join(c.DataPath, p2p.DefaultNetDirName)
privateKeyFile := filepath.Join(c.DataPath, p2p.DefaultNetPrivateKeyFile)
return &p2p.Net{
PrivateKeyFile: privateKeyFile,
MaxPeers: c.Net.MaxPeers,
MaxPendingPeers: c.Net.MaxPendingPeers,
MinConnectedPeers: c.Net.MinConnectedPeers,
Name: fmt.Sprintf("%v %v", metadata.Version, c.Name),
Discovery: c.Net.Discovery, // Add this line
StaticNodes: c.Net.StaticNodes, // Add this line
Seeders: c.Net.Seeders,
NodeDatabase: networkDataDir,
ListenAddr: c.Net.ListenHost,
ListenPort: c.Net.ListenPort,
}
}
3. Update Node Initialization
Modify the node initialization in node/node.go
to use the config values and parse static nodes:
func NewNode(conf *Config) (*Node, error) {
// ... existing code ...
netConfig := conf.makeNetConfig()
nodes, err := netConfig.Nodes()
if err != nil {
return nil, errors.Errorf("Unable to parse seeders. Reason: %v", err)
}
// Parse static nodes using the existing ParseNode functionality
var staticNodes []*discover.Node
if len(netConfig.StaticNodes) > 0 {
staticNodes = make([]*discover.Node, len(netConfig.StaticNodes))
for i, nodeStr := range netConfig.StaticNodes {
staticNodes[i], err = discover.ParseNode(nodeStr)
if err != nil {
return nil, errors.Errorf("Unable to parse static node %s. Reason: %v", nodeStr, err)
}
}
}
node.server = &p2p.Server{
PrivateKey: netConfig.PrivateKey(),
Name: netConfig.Name,
MaxPeers: netConfig.MaxPeers,
MinConnectedPeers: netConfig.MinConnectedPeers,
MaxPendingPeers: netConfig.MaxPendingPeers,
Discovery: netConfig.Discovery, // Use config value
NoDial: false,
StaticNodes: staticNodes, // Use parsed static nodes
BootstrapNodes: nodes,
TrustedNodes: nil,
NodeDatabase: netConfig.NodeDatabase,
ListenAddr: fmt.Sprintf("%v:%v", netConfig.ListenAddr, netConfig.ListenPort),
Protocols: node.z.Protocol().SubProtocols,
}
return node, nil
}
Example config.json
After these changes, you can configure discovery and static nodes in your config.json
like this:
{
"Net": {
"ListenHost": "0.0.0.0",
"ListenPort": 35995,
"MaxPeers": 60,
"MinConnectedPeers": 16,
"MaxPendingPeers": 10,
"Discovery": true,
"StaticNodes": [
"enode://<node-id>@<ip>:<port>",
"enode://<node-id>@<ip>:<port>"
],
"Seeders": [
"enode://<node-id>@<ip>:<port>"
]
}
}
Benefits
- Flexibility: Users can now configure whether to enable or disable peer discovery
- Control: Users can specify static nodes that should always be maintained
- Customization: Different nodes can have different discovery and connection strategies
- Reuse: Leverages existing node parsing functionality that’s already proven to work with seeders
Considerations
- When
Discovery
is set tofalse
, make sure to provide enoughStaticNodes
to maintain network connectivity - The
StaticNodes
list should contain valid enode URLs in the format:enode://<node-id>@<ip>:<port>
- Changes to these settings require a node restart to take effect
- The node parsing functionality is already well-tested through its use with seeders