Refactor node and bootstrap configurations for improved clarity and consistency; unify RQLite and Raft ports across nodes, update environment setup, and enhance logging for node operations.

This commit is contained in:
anonpenguin 2025-08-05 22:28:12 +03:00
parent 0a0756d4da
commit cd74a2df68
7 changed files with 222 additions and 220 deletions

View File

@ -1,17 +1,14 @@
# Bootstrap Node Configuration
# Add multiple bootstrap peers separated by commas for redundancy
# Example Environment Configuration for Development
# Copy this to .env and modify as needed
# Primary bootstrap peer (currently running)
# Bootstrap peers for development (localhost)
BOOTSTRAP_PEERS=/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWN3AQHuxAzXfu98tiFYw7W3N2SyDwdxDRANXJp3ktVf8j
# Example with multiple bootstrap peers:
# BOOTSTRAP_PEERS=/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWN3AQHuxAzXfu98tiFYw7W3N2SyDwdxDRANXJp3ktVf8j,/ip4/127.0.0.1/tcp/4002/p2p/12D3KooWSecondBootstrapPeer,/ip4/192.168.1.100/tcp/4001/p2p/12D3KooWRemoteBootstrapPeer
# For production, you would add external IPs:
# BOOTSTRAP_PEERS=/ip4/bootstrap1.example.com/tcp/4001/p2p/12D3KooWPeer1,/ip4/bootstrap2.example.com/tcp/4001/p2p/12D3KooWPeer2
# Default bootstrap port
BOOTSTRAP_PORT=4001
# Environment (development, staging, production)
# Environment setting (development or production)
ENVIRONMENT=development
# Note: In production, set ENVIRONMENT=production and the bootstrap.go will automatically
# use the production bootstrap peers defined in constants/bootstrap.go
# For multiple bootstrap peers, separate with commas:
# BOOTSTRAP_PEERS=/ip4/127.0.0.1/tcp/4001/p2p/peer1,/ip4/127.0.0.1/tcp/4005/p2p/peer2

View File

@ -1,13 +1,12 @@
# Network - Distributed P2P Database System
# Makefile for development and build tasks
.PHONY: build clean test run-bootstrap run-node run-example deps tidy fmt vet
.PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet
# Build targets
build: deps
@echo "Building network executables..."
@mkdir -p bin
go build -o bin/bootstrap cmd/bootstrap/main.go
go build -o bin/node cmd/node/main.go
go build -o bin/network-cli cmd/cli/main.go
@echo "Build complete!"
@ -24,16 +23,21 @@ test:
@echo "Running tests..."
go test -v ./...
# Run bootstrap node
run-bootstrap:
@echo "Starting bootstrap node..."
go run cmd/bootstrap/main.go -port 4001 -data ./data/bootstrap
# Run regular node
# Run node (auto-detects if bootstrap or regular based on configuration)
run-node:
@echo "Starting regular node..."
@echo "Starting network node..."
go run cmd/node/main.go -data ./data/node
# Run second node with different identity
run-node2:
@echo "Starting second network node..."
go run cmd/node/main.go -id node2
# Run third node with different identity
run-node3:
@echo "Starting third network node..."
go run cmd/node/main.go -id node3
# Show current bootstrap configuration
show-bootstrap:
@echo "Current bootstrap configuration from .env:"
@ -106,7 +110,7 @@ vet:
# Development setup
dev-setup: deps
@echo "Setting up development environment..."
@mkdir -p data/bootstrap data/node1 data/node2
@mkdir -p data/bootstrap data/node data/node-node2 data/node-node3
@mkdir -p data/test-bootstrap data/test-node1 data/test-node2
@mkdir -p anchat/bin
@echo "Development setup complete!"
@ -138,15 +142,16 @@ test-consensus: build
# Start development cluster (requires multiple terminals)
dev-cluster:
@echo "To start a development cluster, run these commands in separate terminals:"
@echo "1. make run-bootstrap # Start bootstrap node"
@echo "2. make run-node # Start regular node (auto-loads bootstrap from .env)"
@echo "3. make run-example # Test basic functionality"
@echo "4. make run-anchat # Start messaging app"
@echo "5. make show-bootstrap # Check bootstrap configuration"
@echo "6. make cli-health # Check network health"
@echo "7. make cli-peers # List peers"
@echo "8. make cli-storage-test # Test storage"
@echo "9. make cli-pubsub-test # Test messaging"
@echo "1. make run-node # Start first node (auto-detects as bootstrap in dev)"
@echo "2. make run-node2 # Start second node with different identity"
@echo "3. make run-node3 # Start third node with different identity"
@echo "4. make run-example # Test basic functionality"
@echo "5. make run-anchat # Start messaging app"
@echo "6. make show-bootstrap # Check bootstrap configuration"
@echo "7. make cli-health # Check network health"
@echo "8. make cli-peers # List peers"
@echo "9. make cli-storage-test # Test storage"
@echo "10. make cli-pubsub-test # Test messaging"
# Full development workflow
dev: clean build build-anchat test
@ -159,8 +164,9 @@ help:
@echo " build-anchat - Build Anchat application"
@echo " clean - Clean build artifacts"
@echo " test - Run tests"
@echo " run-bootstrap - Start bootstrap node"
@echo " run-node - Start regular node (auto-loads bootstrap from .env)"
@echo " run-node - Start network node (auto-detects bootstrap vs regular)"
@echo " run-node2 - Start second node with different identity"
@echo " run-node3 - Start third node with different identity"
@echo " run-example - Run usage example"
@echo " run-anchat - Run Anchat demo"
@echo " run-cli - Run network CLI help"

View File

@ -1,112 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/signal"
"path/filepath"
"syscall"
"git.debros.io/DeBros/network/pkg/config"
"git.debros.io/DeBros/network/pkg/logging"
"git.debros.io/DeBros/network/pkg/node"
)
func main() {
var (
dataDir = flag.String("data", "./data/bootstrap", "Data directory")
port = flag.Int("port", 4001, "Listen port")
help = flag.Bool("help", false, "Show help")
)
flag.Parse()
if *help {
flag.Usage()
return
}
// Create colored logger for bootstrap
logger, err := logging.NewStandardLogger(logging.ComponentBootstrap)
if err != nil {
log.Fatalf("Failed to create logger: %v", err)
}
// Load configuration
cfg := config.BootstrapConfig()
cfg.Node.DataDir = *dataDir
cfg.Node.ListenAddresses = []string{
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *port),
fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", *port),
}
// Configure RQLite ports for bootstrap node
cfg.Database.RQLitePort = *port + 1000 // e.g., 5001 for bootstrap port 4001
cfg.Database.RQLiteRaftPort = *port + 3000 // e.g., 7001 for bootstrap port 4001 (changed to avoid conflicts)
cfg.Database.RQLiteJoinAddress = "" // Bootstrap node doesn't join anyone
logger.Printf("Starting bootstrap node...")
logger.Printf("Data directory: %s", cfg.Node.DataDir)
logger.Printf("Listen addresses: %v", cfg.Node.ListenAddresses)
logger.Printf("RQLite HTTP port: %d", cfg.Database.RQLitePort)
logger.Printf("RQLite Raft port: %d", cfg.Database.RQLiteRaftPort)
// Create context for graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Start bootstrap node in a goroutine
errChan := make(chan error, 1)
go func() {
if err := startBootstrapNode(ctx, cfg, *port, logger); err != nil {
errChan <- err
}
}()
// Wait for interrupt signal or error
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
select {
case err := <-errChan:
logger.Printf("Failed to start bootstrap node: %v", err)
os.Exit(1)
case <-c:
logger.Printf("Shutting down bootstrap node...")
cancel()
}
}
func startBootstrapNode(ctx context.Context, cfg *config.Config, port int, logger *logging.StandardLogger) error {
// Create and start bootstrap node using the new node implementation
n, err := node.NewNode(cfg)
if err != nil {
return fmt.Errorf("failed to create bootstrap node: %w", err)
}
if err := n.Start(ctx); err != nil {
return fmt.Errorf("failed to start bootstrap node: %w", err)
}
// Save the peer ID to a file for CLI access
peerID := n.GetPeerID()
peerInfoFile := filepath.Join(cfg.Node.DataDir, "peer.info")
peerMultiaddr := fmt.Sprintf("/ip4/127.0.0.1/tcp/%d/p2p/%s", port, peerID)
if err := os.WriteFile(peerInfoFile, []byte(peerMultiaddr), 0644); err != nil {
logger.Printf("Warning: Failed to save peer info: %v", err)
} else {
logger.Printf("Peer info saved to: %s", peerInfoFile)
logger.Printf("Bootstrap multiaddr: %s", peerMultiaddr)
}
logger.Printf("Bootstrap node started successfully")
// Wait for context cancellation
<-ctx.Done()
// Stop node
return n.Stop()
}

View File

@ -7,18 +7,21 @@ import (
"log"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"git.debros.io/DeBros/network/pkg/config"
"git.debros.io/DeBros/network/pkg/constants"
"git.debros.io/DeBros/network/pkg/logging"
"git.debros.io/DeBros/network/pkg/node"
)
func main() {
var (
dataDir = flag.String("data", "./data/node", "Data directory")
port = flag.Int("port", 4002, "Listen port")
bootstrap = flag.String("bootstrap", "", "Bootstrap peer address")
dataDir = flag.String("data", "", "Data directory (auto-detected if not provided)")
nodeID = flag.String("id", "", "Node identifier (for running multiple local nodes)")
bootstrap = flag.String("bootstrap", "", "Bootstrap peer address (for manual override)")
help = flag.Bool("help", false, "Show help")
)
flag.Parse()
@ -28,48 +31,88 @@ func main() {
return
}
// Load configuration
cfg := config.DefaultConfig()
// Auto-detect if this is a bootstrap node based on configuration
isBootstrap := isBootstrapNode()
// Set default data directory if not provided
if *dataDir == "" {
if isBootstrap {
*dataDir = "./data/bootstrap"
} else {
if *nodeID != "" {
*dataDir = fmt.Sprintf("./data/node-%s", *nodeID)
} else {
*dataDir = "./data/node"
}
}
}
// All nodes use port 4001 for consistency
port := 4001
// Create logger with appropriate component type
var logger *logging.StandardLogger
var err error
if isBootstrap {
logger, err = logging.NewStandardLogger(logging.ComponentBootstrap)
} else {
logger, err = logging.NewStandardLogger(logging.ComponentNode)
}
if err != nil {
log.Fatalf("Failed to create logger: %v", err)
}
// Load configuration based on node type
var cfg *config.Config
if isBootstrap {
cfg = config.BootstrapConfig()
logger.Printf("Starting bootstrap node...")
} else {
cfg = config.DefaultConfig()
logger.Printf("Starting regular node...")
}
// Set basic configuration
cfg.Node.DataDir = *dataDir
cfg.Node.ListenAddresses = []string{
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *port),
fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", *port),
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port),
fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", port),
}
// Configure RQLite ports based on node port (only if not already set in config)
if cfg.Database.RQLitePort == 0 {
cfg.Database.RQLitePort = *port + 1000 // e.g., 5002 for node port 4002
}
if cfg.Database.RQLiteRaftPort == 0 {
cfg.Database.RQLiteRaftPort = *port + 3000 // e.g., 7002 for node port 4002 (changed to avoid conflicts)
}
// All nodes use the same RQLite port (4001) to join the same cluster
cfg.Database.RQLitePort = 4001
cfg.Database.RQLiteRaftPort = 4002
// Configure bootstrap peers
if isBootstrap {
// Bootstrap node doesn't join anyone - it starts the cluster
cfg.Database.RQLiteJoinAddress = ""
logger.Printf("Bootstrap node - starting new RQLite cluster")
} else {
// Regular nodes join the bootstrap node's RQLite cluster
cfg.Database.RQLiteJoinAddress = "http://localhost:4001"
// Configure bootstrap peers for P2P discovery
if *bootstrap != "" {
// Use command line bootstrap if provided
cfg.Discovery.BootstrapPeers = []string{*bootstrap}
log.Printf("Using command line bootstrap peer: %s", *bootstrap)
logger.Printf("Using command line bootstrap peer: %s", *bootstrap)
} else {
// Use environment-configured bootstrap peers
bootstrapPeers := constants.GetBootstrapPeers()
if len(bootstrapPeers) > 0 {
cfg.Discovery.BootstrapPeers = bootstrapPeers
log.Printf("Using environment bootstrap peers: %v", bootstrapPeers)
logger.Printf("Using environment bootstrap peers: %v", bootstrapPeers)
} else {
log.Printf("Warning: No bootstrap peers configured")
logger.Printf("Warning: No bootstrap peers configured")
}
}
logger.Printf("Regular node - joining RQLite cluster at: %s", cfg.Database.RQLiteJoinAddress)
}
// For LibP2P peer discovery testing, don't join RQLite cluster
// Each node will have its own independent RQLite instance
cfg.Database.RQLiteJoinAddress = "" // Keep RQLite independent
log.Printf("Starting network node...")
log.Printf("Data directory: %s", cfg.Node.DataDir)
log.Printf("Listen addresses: %v", cfg.Node.ListenAddresses)
log.Printf("Bootstrap peers: %v", cfg.Discovery.BootstrapPeers)
log.Printf("RQLite HTTP port: %d", cfg.Database.RQLitePort)
log.Printf("RQLite Raft port: %d", cfg.Database.RQLiteRaftPort)
logger.Printf("Data directory: %s", cfg.Node.DataDir)
logger.Printf("Listen addresses: %v", cfg.Node.ListenAddresses)
logger.Printf("RQLite HTTP port: %d", cfg.Database.RQLitePort)
logger.Printf("RQLite Raft port: %d", cfg.Database.RQLiteRaftPort)
// Create context for graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
@ -78,7 +121,7 @@ func main() {
// Start node in a goroutine
errChan := make(chan error, 1)
go func() {
if err := startNode(ctx, cfg); err != nil {
if err := startNode(ctx, cfg, port, isBootstrap, logger); err != nil {
errChan <- err
}
}()
@ -89,15 +132,60 @@ func main() {
select {
case err := <-errChan:
log.Fatalf("Failed to start node: %v", err)
logger.Printf("Failed to start node: %v", err)
os.Exit(1)
case <-c:
log.Printf("Shutting down node...")
logger.Printf("Shutting down node...")
cancel()
}
}
func startNode(ctx context.Context, cfg *config.Config) error {
// Create and start node
// isBootstrapNode determines if this should be a bootstrap node
// by checking the local machine's configuration and bootstrap peer list
func isBootstrapNode() bool {
// Get the bootstrap peer addresses to check if this machine should be a bootstrap
bootstrapPeers := constants.GetBootstrapPeers()
// Check if any bootstrap peer is localhost/127.0.0.1 (development)
// or if we're running on a production bootstrap server
hostname, _ := os.Hostname()
for _, peerAddr := range bootstrapPeers {
// Parse the multiaddr to extract the host
host := parseHostFromMultiaddr(peerAddr)
// Check if this is a local bootstrap (development)
if host == "127.0.0.1" || host == "localhost" {
return true // In development, assume we're running the bootstrap
}
// Check if this is a production bootstrap server
// You could add more sophisticated host matching here
if hostname != "" && strings.Contains(peerAddr, hostname) {
return true
}
}
// Default: if no specific match, run as regular node
return false
}
// parseHostFromMultiaddr extracts the host from a multiaddr
func parseHostFromMultiaddr(multiaddr string) string {
// Simple parsing for /ip4/host/tcp/port/p2p/peerid format
parts := strings.Split(multiaddr, "/")
// Look for ip4/ip6/dns host in the multiaddr
for i, part := range parts {
if (part == "ip4" || part == "ip6" || part == "dns" || part == "dns4" || part == "dns6") && i+1 < len(parts) {
return parts[i+1]
}
}
return ""
}
func startNode(ctx context.Context, cfg *config.Config, port int, isBootstrap bool, logger *logging.StandardLogger) error {
// Create and start node using the unified node implementation
n, err := node.NewNode(cfg)
if err != nil {
return fmt.Errorf("failed to create node: %w", err)
@ -107,6 +195,22 @@ func startNode(ctx context.Context, cfg *config.Config) error {
return fmt.Errorf("failed to start node: %w", err)
}
// Save the peer ID to a file for CLI access (especially useful for bootstrap)
if isBootstrap {
peerID := n.GetPeerID()
peerInfoFile := filepath.Join(cfg.Node.DataDir, "peer.info")
peerMultiaddr := fmt.Sprintf("/ip4/127.0.0.1/tcp/%d/p2p/%s", port, peerID)
if err := os.WriteFile(peerInfoFile, []byte(peerMultiaddr), 0644); err != nil {
logger.Printf("Warning: Failed to save peer info: %v", err)
} else {
logger.Printf("Peer info saved to: %s", peerInfoFile)
logger.Printf("Bootstrap multiaddr: %s", peerMultiaddr)
}
}
logger.Printf("Node started successfully")
// Wait for context cancellation
<-ctx.Done()

View File

@ -3,11 +3,12 @@ package client
import (
"context"
"fmt"
"os"
"strings"
"sync"
"time"
"git.debros.io/DeBros/network/pkg/constants"
"git.debros.io/DeBros/network/pkg/storage"
"github.com/libp2p/go-libp2p/core/peer"
@ -196,29 +197,35 @@ func (d *DatabaseClientImpl) connectToAvailableNode() (*gorqlite.Connection, err
return nil, fmt.Errorf("failed to connect to any RQLite instance. Last error: %w", lastErr)
}
// getRQLiteNodes returns a list of RQLite node URLs from environment or defaults
// getRQLiteNodes returns a list of RQLite node URLs using the peer IPs/hostnames from bootstrap.go, always on port 4001
func (d *DatabaseClientImpl) getRQLiteNodes() []string {
// Try to get RQLite nodes from environment variable
if envNodes := os.Getenv("RQLITE_NODES"); envNodes != "" {
return strings.Split(envNodes, ",")
// Use bootstrap peer addresses from constants
// Import the constants package
// We'll extract the IP/host from the multiaddr and build the HTTP URL
var nodes []string
for _, addr := range constants.GetBootstrapPeers() {
// Example multiaddr: /ip4/57.129.81.31/tcp/4001/p2p/12D3KooWQRK2duw5B5LXi8gA7HBBFiCsLvwyph2ZU9VBmvbE1Nei
parts := strings.Split(addr, "/")
var host string
var port string = "4001" // always use 4001
for i := 0; i < len(parts); i++ {
if parts[i] == "ip4" || parts[i] == "ip6" {
host = parts[i+1]
}
// Check if we're in production environment
if env := os.Getenv("ENVIRONMENT"); env == "production" {
// Use production servers with RQLite HTTP API ports (network port + 1000)
return []string{
"http://57.129.81.31:5001", // production server 1
"http://38.242.250.186:5001", // production server 2
if parts[i] == "dns" || parts[i] == "dns4" || parts[i] == "dns6" {
host = parts[i+1]
}
// ignore tcp port in multiaddr, always use 4001
}
if host != "" {
nodes = append(nodes, "http://"+host+":"+port)
}
}
// Fallback to localhost for development
return []string{
"http://localhost:5001", // bootstrap
"http://localhost:5002", // node1
"http://localhost:5003", // node2
"http://localhost:5004", // node3 (if exists)
// If no peers found, fallback to localhost:4001
if len(nodes) == 0 {
nodes = append(nodes, "http://localhost:4001")
}
return nodes
}
// testConnection performs a health check on the RQLite connection

View File

@ -101,10 +101,10 @@ func setDefaultBootstrapConfig() {
} else {
// Development: only use localhost bootstrap
BootstrapPeerIDs = []string{
"12D3KooWN3AQHuxAzXfu98tiFYw7W3N2SyDwdxDRANXJp3ktVf8j",
"12D3KooWBQAr9Lj9Z3918wBT523tJaRiPN6zRywAtttvPrwcZfJb",
}
BootstrapAddresses = []string{
"/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWN3AQHuxAzXfu98tiFYw7W3N2SyDwdxDRANXJp3ktVf8j",
"/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWBQAr9Lj9Z3918wBT523tJaRiPN6zRywAtttvPrwcZfJb",
}
}
BootstrapPort = 4001

View File

@ -16,8 +16,8 @@ INSTALL_DIR="/opt/debros"
REPO_URL="https://git.debros.io/DeBros/network.git"
MIN_GO_VERSION="1.19"
NODE_PORT="4001"
RQLITE_NODE_PORT="5001"
RAFT_NODE_PORT="7001"
RQLITE_PORT="4001" # All nodes use same RQLite port to join same cluster
RAFT_PORT="4002" # All nodes use same Raft port
UPDATE_MODE=false
NON_INTERACTIVE=false
@ -372,7 +372,7 @@ install_rqlite() {
# Check port availability
check_ports() {
local ports=($NODE_PORT $RQLITE_NODE_PORT $RAFT_NODE_PORT)
local ports=($NODE_PORT $RQLITE_PORT $RAFT_PORT)
for port in "${ports[@]}"; do
if sudo netstat -tuln 2>/dev/null | grep -q ":$port " || ss -tuln 2>/dev/null | grep -q ":$port "; then
@ -622,8 +622,8 @@ node:
solana_wallet: "$SOLANA_WALLET"
database:
rqlite_port: $RQLITE_NODE_PORT
rqlite_raft_port: $RAFT_NODE_PORT
rqlite_port: $RQLITE_PORT
rqlite_raft_port: $RAFT_PORT
logging:
level: "info"
@ -647,7 +647,7 @@ configure_firewall() {
log "Adding UFW rules for DeBros Network ports..."
# Add ports for node
for port in $NODE_PORT $RQLITE_NODE_PORT $RAFT_NODE_PORT; do
for port in $NODE_PORT $RQLITE_PORT $RAFT_PORT; do
if ! sudo ufw allow $port; then
error "Failed to allow port $port"
exit 1
@ -668,7 +668,7 @@ configure_firewall() {
warning "UFW not found. Please configure firewall manually."
log "Required ports to allow:"
log " - Port $NODE_PORT (Node)"
log " - Port $RQLITE_NODE_PORT (RQLite)"
log " - Port $RQLITE_PORT (RQLite)"
log " - Port $RAFT_NODE_PORT (Raft)"
fi
fi
@ -827,7 +827,7 @@ main() {
log "${GREEN}Logs:${NOCOLOR} ${CYAN}$INSTALL_DIR/logs/$NODE_TYPE.log${NOCOLOR}"
log "${GREEN}Node Port:${NOCOLOR} ${CYAN}$NODE_PORT${NOCOLOR}"
log "${GREEN}RQLite Port:${NOCOLOR} ${CYAN}$RQLITE_NODE_PORT${NOCOLOR}"
log "${GREEN}RQLite Port:${NOCOLOR} ${CYAN}$RQLITE_PORT${NOCOLOR}"
log "${GREEN}Raft Port:${NOCOLOR} ${CYAN}$RAFT_NODE_PORT${NOCOLOR}"
log "${BLUE}==================================================${NOCOLOR}"