From 0505740f1ef7e93dbb7837957daf7113c23f4e51 Mon Sep 17 00:00:00 2001
From: Barry Deen <barrydeen@protonmail.com>
Date: Sat, 7 Sep 2024 23:39:23 -0400
Subject: [PATCH] cleaner logic, fix segfault, update readme

---
 .env.example | 12 ++++++-
 README.md    | 68 +++++++++++++++++++++++++++++++++----
 main.go      | 95 +++++++++++++++++++++++++++++++++-------------------
 3 files changed, 133 insertions(+), 42 deletions(-)

diff --git a/.env.example b/.env.example
index 6eeee36..f18144d 100644
--- a/.env.example
+++ b/.env.example
@@ -1,7 +1,17 @@
+# Relay Metadata
 RELAY_NAME="utxo WoT relay"
-RELAY_PUBKEY="e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"
+RELAY_PUBKEY="e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb" # not your npub!
 RELAY_DESCRIPTION="Only notes in utxo WoT"
 RELAY_URL="wss://wot.utxo.one"
+
+# where we should store the database
 DB_PATH="db"
+
+# where we should store the index.html and static files
 INDEX_PATH="templates/index.html"
 STATIC_PATH="templates/static"
+
+# relay behavior
+
+# how often to refresh the relay's view of the WoT in HOURS
+REFRESH_INTERVAL_HOURS=24
\ No newline at end of file
diff --git a/README.md b/README.md
index 46a4aa8..c0dfc2e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# WOT Relay
+# WoT Relay
 
-WOT Relay is a Nostr relay that saves all the notes that people you follow, and people they follow are posting.
+WOT Relay is a Nostr relay that saves all the notes that people you follow, and people they follow are posting. It's built on the [Khatru](https://khatru.nostr.technology) framework.
 
 ## Prerequisites
 
@@ -37,6 +37,7 @@ RELAY_DESCRIPTION="Your relay description"
 DB_PATH="/home/ubuntu/wot-relay/db" # any path you would like the database to be saved.
 INDEX_PATH="/home/ubuntu/wot-relay/templates/index.html" # path to the index.html file
 STATIC_PATH="/home/ubuntu/wot-relay/templates/static" # path to the static folder
+REFRESH_INTERVAL=24 # interval in hours to refresh the web of trust
 ```
 
 ### 4. Build the project
@@ -65,10 +66,9 @@ Description=WOT Relay Service
 After=network.target
 
 [Service]
-ExecStart=/path/to/wot-relay
-WorkingDirectory=/path/to/wot-relay
+ExecStart=/home/ubuntu/wot-relay/wot-relay #change this to your path
+WorkingDirectory=/home/ubuntu/wot-relay #change this to your path
 Restart=always
-EnvironmentFile=/path/to/.env
 
 [Install]
 WantedBy=multi-user.target
@@ -94,7 +94,63 @@ sudo systemctl start wot-relay
 sudo systemctl enable wot-relay
 ```
 
-### 6. Start the Project with Docker Compose (optional)
+#### Permission Issues on Some Systems
+
+the relay may not have permissions to read and write to the database. To fix this, you can change the permissions of the database folder:
+
+```bash
+sudo chmod -R 777 /path/to/db
+```
+
+### 6. Serving over nginx (optional)
+
+You can serve the relay over nginx by adding the following configuration to your nginx configuration file:
+
+```nginx
+server {
+    listen 80;
+    server_name yourdomain.com;
+
+    location / {
+        proxy_pass http://localhost:3334;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+    }
+}
+```
+
+Replace `yourdomain.com` with your actual domain name.
+
+After adding the configuration, restart nginx:
+
+```bash
+sudo systemctl restart nginx
+```
+
+### 7. Install Certbot (optional)
+
+If you want to serve the relay over HTTPS, you can use Certbot to generate an SSL certificate.
+
+```bash
+sudo apt-get update
+sudo apt-get install certbot python3-certbot-nginx
+```
+
+After installing Certbot, run the following command to generate an SSL certificate:
+
+```bash
+sudo certbot --nginx
+```
+
+Follow the instructions to generate the certificate.
+
+### 8. Access the relay
+
+Once everything is set up, the relay will be running on `localhost:3334` or your domain name if you set up nginx.
+
+## Start the Project with Docker Compose
 
 To start the project using Docker Compose, follow these steps:
 
diff --git a/main.go b/main.go
index 83b376a..81c5899 100644
--- a/main.go
+++ b/main.go
@@ -7,6 +7,7 @@ import (
 	"log"
 	"net/http"
 	"os"
+	"strconv"
 	"sync"
 	"time"
 
@@ -26,6 +27,7 @@ type Config struct {
 	RelayURL         string
 	IndexPath        string
 	StaticPath       string
+	RefreshInterval  int
 }
 
 var pool *nostr.SimplePool
@@ -36,19 +38,27 @@ var mu sync.Mutex
 var trustNetworkFilter *blobloom.Filter
 var trustNetworkFilterMu sync.Mutex
 var seedRelays []string
+var booted bool
+var oneHopNetwork []string
 
 func main() {
+	booted = false
 	green := "\033[32m"
 	reset := "\033[0m"
 
-	art := `                               
- __      __      ___.             _____  ___________                      __   
-/  \    /  \ ____\_ |__     _____/ ____\ \__    ___/______ __ __  _______/  |_ 
-\   \/\/   // __ \| __ \   /  _ \   __\    |    |  \_  __ \  |  \/  ___/\   __\
- \        /\  ___/| \_\ \ (  <_> )  |      |    |   |  | \/  |  /\___ \  |  |  
-  \__/\  /  \___  >___  /  \____/|__|      |____|   |__|  |____//____  > |__|  
-       \/       \/    \/                                             \/            
-		`
+	art := `
+888       888      88888888888      8888888b.          888                   
+888   o   888          888          888   Y88b         888                   
+888  d8b  888          888          888    888         888                   
+888 d888b 888  .d88b.  888          888   d88P .d88b.  888  8888b.  888  888 
+888d88888b888 d88""88b 888          8888888P" d8P  Y8b 888     "88b 888  888 
+88888P Y88888 888  888 888          888 T88b  88888888 888 .d888888 888  888 
+8888P   Y8888 Y88..88P 888          888  T88b Y8b.     888 888  888 Y88b 888 
+888P     Y888  "Y88P"  888          888   T88b "Y8888  888 "Y888888  "Y88888 
+                                                                         888 
+                                                                    Y8b d88P 
+                                               powered by: khatru     "Y88P"  
+	`
 
 	fmt.Println(green + art + reset)
 	log.Println("🚀 booting up web of trust relay")
@@ -104,7 +114,6 @@ func main() {
 	mu.Unlock()
 
 	go refreshTrustNetwork(relay, ctx)
-	go archiveTrustedNotes(relay, ctx)
 
 	mux := relay.Router()
 	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
@@ -138,6 +147,13 @@ func main() {
 func LoadConfig() Config {
 	godotenv.Load(".env")
 
+	if os.Getenv("REFRESH_INTERVAL_HOURS") == "" {
+		os.Setenv("REFRESH_INTERVAL_HOURS", "24")
+	}
+
+	refreshInterval, _ := strconv.Atoi(os.Getenv("REFRESH_INTERVAL_HOURS"))
+	log.Println("🔄 refresh interval set to", refreshInterval, "hours")
+
 	config := Config{
 		RelayName:        getEnv("RELAY_NAME"),
 		RelayPubkey:      getEnv("RELAY_PUBKEY"),
@@ -146,6 +162,7 @@ func LoadConfig() Config {
 		RelayURL:         getEnv("RELAY_URL"),
 		IndexPath:        getEnv("INDEX_PATH"),
 		StaticPath:       getEnv("STATIC_PATH"),
+		RefreshInterval:  refreshInterval,
 	}
 
 	return config
@@ -174,10 +191,10 @@ func updateTrustNetworkFilter() {
 	}
 }
 
-func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) []string {
+func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) {
 
 	runTrustNetworkRefresh := func() {
-		timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
+		timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Second)
 		defer cancel()
 
 		filters := []nostr.Filter{{
@@ -188,25 +205,22 @@ func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) []string {
 		log.Println("🔍 fetching owner's follows")
 		for ev := range pool.SubManyEose(timeoutCtx, seedRelays, filters) {
 			for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) {
-				appendPubkey(contact[1])
+				appendOneHopNetwork(contact[1])
 			}
 		}
 
-		follows := make([]string, len(trustNetwork))
-		copy(follows, trustNetwork)
-
 		log.Println("🌐 building web of trust graph")
-		for i := 0; i < len(follows); i += 200 {
+		for i := 0; i < len(oneHopNetwork); i += 100 {
 			timeout, cancel := context.WithTimeout(ctx, 3*time.Second)
 			defer cancel()
 
-			end := i + 200
-			if end > len(follows) {
-				end = len(follows)
+			end := i + 100
+			if end > len(oneHopNetwork) {
+				end = len(oneHopNetwork)
 			}
 
 			filters = []nostr.Filter{{
-				Authors: follows[i:end],
+				Authors: oneHopNetwork[i:end],
 				Kinds:   []int{nostr.KindContactList, nostr.KindRelayListMetadata, nostr.KindProfileMetadata},
 			}}
 
@@ -231,18 +245,11 @@ func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) []string {
 		log.Println("🔗 relays discovered:", len(relays))
 	}
 
-	runTrustNetworkRefresh()
-	updateTrustNetworkFilter()
-
-	ticker := time.NewTicker(24 * time.Hour)
-	defer ticker.Stop()
-
-	for range ticker.C {
+	for {
 		runTrustNetworkRefresh()
 		updateTrustNetworkFilter()
+		archiveTrustedNotes(relay, ctx)
 	}
-
-	return trustNetwork
 }
 
 func appendRelay(relay string) {
@@ -274,10 +281,25 @@ func appendPubkey(pubkey string) {
 	trustNetwork = append(trustNetwork, pubkey)
 }
 
+func appendOneHopNetwork(pubkey string) {
+	mu.Lock()
+	defer mu.Unlock()
+
+	for _, pk := range oneHopNetwork {
+		if pk == pubkey {
+			return
+		}
+	}
+
+	if len(pubkey) != 64 {
+		return
+	}
+
+	oneHopNetwork = append(oneHopNetwork, pubkey)
+}
+
 func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) {
-	log.Println("⏳ waiting for trust network to be populated")
-	time.Sleep(1 * time.Minute)
-	timeout, cancel := context.WithTimeout(ctx, 24*time.Hour)
+	timeout, cancel := context.WithTimeout(ctx, time.Duration(config.RefreshInterval)*time.Hour)
 	defer cancel()
 
 	filters := []nostr.Filter{{
@@ -297,7 +319,8 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) {
 	}}
 
 	log.Println("📦 archiving trusted notes...")
-	var i int64
+	var trustedNotes uint64
+	var untrustedNotes uint64
 	trustNetworkFilterMu.Lock()
 	for ev := range pool.SubMany(timeout, seedRelays, filters) {
 		if trustNetworkFilter.Has(xxhash.Sum64([]byte(ev.Event.PubKey))) {
@@ -305,9 +328,11 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) {
 				continue
 			}
 			relay.AddEvent(ctx, ev.Event)
-			i++
+			trustedNotes++
+		} else {
+			untrustedNotes++
 		}
 	}
 	trustNetworkFilterMu.Unlock()
-	fmt.Println("📦 archived", i, "trusted notes")
+	log.Println("📦 archived", trustedNotes, "trusted notes and discarded", untrustedNotes, "untrusted notes")
 }