Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ bin/
# Environment files
.env
.env.*

.DS_Store
# XML files (repomix output)
*.xml
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.24.5-alpine AS builder
FROM golang:1.25-alpine AS builder

WORKDIR /app

Expand Down
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ INFO[2025-07-10T17:16:04+05:30] Listening on addresses: [/ip4/127.0.0.1/tcp/4001
From the example above, a full multiaddress to use for other nodes would be:
`/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWCNsSau1o9MeMVpHudvHaZRLESRcaGVK9FPKhdLU36BtF`

## Debugging Memory / Performance

### Periodic Status Logs

Every 60 seconds the node logs a status line with key metrics:

```
Status: connected=150 peerstore=152 dht_rt=20 goroutines=45 heap_alloc=28MB heap_inuse=32MB sys=55MB
```

| Metric | What to watch for |
|---|---|
| `peerstore` growing >> `connected` | Peerstore GC not cleaning fast enough |
| `dht_rt` growing unbounded | DHT routing table accumulating entries |
| `goroutines` growing | Goroutine leak |
| `heap_alloc` growing while others stable | Leak in libp2p internals (gossipsub, relay, etc.) |

### pprof Endpoint

Set `PPROF_PORT=6060` in your `.env` file to enable the Go pprof debug server. The port is already wired in `docker-compose.yaml`.

```bash
# Heap profile — what's using memory right now
go tool pprof http://localhost:6060/debug/pprof/heap

# Allocations — what's been allocating the most over time
go tool pprof -alloc_space http://localhost:6060/debug/pprof/heap

# Compare two snapshots to find what grew (most useful)
curl -o heap1.pb.gz http://localhost:6060/debug/pprof/heap
# ... wait 30 min ...
curl -o heap2.pb.gz http://localhost:6060/debug/pprof/heap
go tool pprof -base heap1.pb.gz heap2.pb.gz

# Goroutine dump
curl http://localhost:6060/debug/pprof/goroutine?debug=2
```

The pprof diff (`-base`) is the most powerful — it shows exactly which allocations grew in the window, narrowing down whether the source is peerstore, DHT, gossipsub, relay, or something else.

## Usage with Other Nodes

To configure other libp2p nodes (like the `snapshotter-lite-local-collector` or `submission-topic-watcher`) to use this bootstrap node, you typically pass its full multiaddress via a command-line flag or environment variable (e.g., `--bootstrap` flag for the watcher, or `BOOTSTRAP_NODE_ADDR` environment variable for the collector).
12 changes: 11 additions & 1 deletion build-docker.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
#!/bin/bash

# Detect docker compose command (docker compose plugin vs docker-compose standalone)
if docker compose version >/dev/null 2>&1; then
DOCKER_COMPOSE="docker compose"
elif docker-compose version >/dev/null 2>&1; then
DOCKER_COMPOSE="docker-compose"
else
echo "Error: Neither 'docker compose' nor 'docker-compose' found. Please install Docker Compose."
exit 1
fi

# Build the Docker image using docker-compose
docker-compose build
$DOCKER_COMPOSE build
33 changes: 29 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import (
"encoding/hex"
"flag"
"fmt"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"runtime"
"strconv"
"submissions-bootstrap-node/pkg/config"
"submissions-bootstrap-node/pkg/service"
Expand Down Expand Up @@ -117,17 +120,36 @@ func main() {
log.Infof("🚀 Bootstrap node started. ID: %s", node.Host.ID().String())
log.Infof("🌍 Listening on addresses: %s", node.Host.Addrs())

// Start periodic peer logging
// Start pprof debug server if PPROF_PORT is set
if pprofPort := os.Getenv("PPROF_PORT"); pprofPort != "" {
go func() {
addr := ":" + pprofPort
log.Infof("Starting pprof server on %s", addr)
if err := http.ListenAndServe(addr, nil); err != nil {
log.Errorf("pprof server failed: %v", err)
}
}()
}

// Start periodic peer and memory logging
go func() {
ticker := time.NewTicker(60 * time.Second) // Log every 10 seconds
ticker := time.NewTicker(60 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
peers := node.Host.Network().Peers()
log.Infof("Connected peers: %d", len(peers))
peerstoreSize := len(node.Host.Peerstore().Peers())
dhtSize := node.DHT.RoutingTable().Size()

var m runtime.MemStats
runtime.ReadMemStats(&m)

log.Infof("Status: connected=%d peerstore=%d dht_rt=%d goroutines=%d heap_alloc=%dMB heap_inuse=%dMB sys=%dMB",
len(peers), peerstoreSize, dhtSize, runtime.NumGoroutine(),
m.HeapAlloc/1024/1024, m.HeapInuse/1024/1024, m.Sys/1024/1024)
for _, p := range peers {
log.Debugf(" - %s", p.String())
}
Expand All @@ -139,8 +161,11 @@ func main() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
signal.Stop(sigs)

fmt.Println()
log.Info("Shutting down bootstrap node...")
node.Host.Close()
if err := node.Close(); err != nil {
log.Errorf("Error during shutdown: %v", err)
}
}
10 changes: 8 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
version: '3.8'

services:
bootstrap-node:
build:
context: .
dockerfile: Dockerfile
ports:
- "${BOOTSTRAP_PORT:-4001}:${BOOTSTRAP_PORT:-4001}"
- "${PPROF_PORT:-6060}:${PPROF_PORT:-6060}"
env_file:
- ./.env
restart: unless-stopped
Expand All @@ -17,3 +16,10 @@ services:
environment:
- LOG_FILE=/app/logs/bootstrap-node.log
- LOG_LEVEL=${LOG_LEVEL:-info}
- PPROF_PORT=${PPROF_PORT:-6060}
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
compress: "true"
Loading