wot-relay/main.go

314 lines
7.5 KiB
Go
Raw Normal View History

2024-09-06 11:16:40 -04:00
package main
import (
"context"
"fmt"
2024-09-06 19:25:40 -04:00
"html/template"
2024-09-06 11:16:40 -04:00
"log"
"net/http"
"os"
"sync"
"time"
2024-09-07 13:21:22 -04:00
"github.com/cespare/xxhash"
2024-09-06 11:16:40 -04:00
"github.com/fiatjaf/eventstore/lmdb"
"github.com/fiatjaf/khatru"
2024-09-07 13:21:22 -04:00
"github.com/greatroar/blobloom"
2024-09-06 11:16:40 -04:00
"github.com/joho/godotenv"
"github.com/nbd-wtf/go-nostr"
)
type Config struct {
RelayName string
RelayPubkey string
RelayDescription string
DBPath string
2024-09-06 19:25:40 -04:00
RelayURL string
2024-09-06 19:33:29 -04:00
IndexPath string
StaticPath string
2024-09-06 11:16:40 -04:00
}
var pool *nostr.SimplePool
2024-09-06 11:16:40 -04:00
var relays []string
var config Config
var trustNetwork []string
var mu sync.Mutex
var trustNetworkFilter *blobloom.Filter
var trustNetworkFilterMu sync.Mutex
var seedRelays []string
2024-09-06 11:16:40 -04:00
func main() {
green := "\033[32m"
reset := "\033[0m"
art := `
__ __ ___. _____ ___________ __
/ \ / \ ____\_ |__ _____/ ____\ \__ ___/______ __ __ _______/ |_
\ \/\/ // __ \| __ \ / _ \ __\ | | \_ __ \ | \/ ___/\ __\
\ /\ ___/| \_\ \ ( <_> ) | | | | | \/ | /\___ \ | |
\__/\ / \___ >___ / \____/|__| |____| |__| |____//____ > |__|
\/ \/ \/ \/
`
fmt.Println(green + art + reset)
log.Println("🚀 booting up web of trust relay")
2024-09-06 11:16:40 -04:00
relay := khatru.NewRelay()
ctx := context.Background()
pool = nostr.NewSimplePool(ctx)
2024-09-06 11:16:40 -04:00
config = LoadConfig()
relay.Info.Name = config.RelayName
relay.Info.PubKey = config.RelayPubkey
relay.Info.Description = config.RelayDescription
appendPubkey(config.RelayPubkey)
2024-09-06 11:16:40 -04:00
db := lmdb.LMDBBackend{
Path: config.DBPath,
2024-09-06 11:16:40 -04:00
}
if err := db.Init(); err != nil {
panic(err)
}
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
2024-09-06 11:16:40 -04:00
relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) {
2024-09-06 17:39:39 -04:00
for _, pk := range trustNetwork {
2024-09-06 11:16:40 -04:00
if pk == event.PubKey {
return false, ""
}
}
return true, "you are not in the web of trust"
})
mu.Lock()
seedRelays = []string{
2024-09-06 11:16:40 -04:00
"wss://nos.lol",
"wss://nostr.mom",
"wss://purplepag.es",
"wss://purplerelay.com",
"wss://relay.damus.io",
"wss://relay.mostr.pub",
"wss://relay.nos.social",
"wss://relay.nostr.band",
"wss://relay.snort.social",
"wss://relayable.org",
"wss://pyramid.fiatjaf.com",
"wss://relay.primal.net",
"wss://relay.nostr.bg",
"wss://no.str.cr",
"wss://nostr21.com",
"wss://nostrue.com",
"wss://relay.siamstr.com",
}
mu.Unlock()
2024-09-06 11:16:40 -04:00
go refreshTrustNetwork(relay, ctx)
go archiveTrustedNotes(relay, ctx)
2024-09-06 11:16:40 -04:00
2024-09-06 19:25:40 -04:00
mux := relay.Router()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles(os.Getenv("INDEX_PATH")))
data := struct {
RelayName string
RelayPubkey string
RelayDescription string
RelayURL string
}{
RelayName: config.RelayName,
RelayPubkey: config.RelayPubkey,
RelayDescription: config.RelayDescription,
RelayURL: config.RelayURL,
}
err := tmpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
2024-09-06 19:33:29 -04:00
mux.Handle("/favicon.ico", http.StripPrefix("/", http.FileServer(http.Dir(config.StaticPath))))
2024-09-06 19:25:40 -04:00
log.Println("🎉 relay running on port :3334")
2024-09-06 19:38:39 -04:00
err := http.ListenAndServe(":3334", relay)
if err != nil {
log.Fatal(err)
}
2024-09-06 11:16:40 -04:00
}
func LoadConfig() Config {
godotenv.Load(".env")
2024-09-06 11:16:40 -04:00
config := Config{
RelayName: getEnv("RELAY_NAME"),
RelayPubkey: getEnv("RELAY_PUBKEY"),
RelayDescription: getEnv("RELAY_DESCRIPTION"),
DBPath: getEnv("DB_PATH"),
2024-09-06 19:25:40 -04:00
RelayURL: getEnv("RELAY_URL"),
2024-09-06 19:33:29 -04:00
IndexPath: getEnv("INDEX_PATH"),
StaticPath: getEnv("STATIC_PATH"),
2024-09-06 11:16:40 -04:00
}
return config
}
func getEnv(key string) string {
value, exists := os.LookupEnv(key)
if !exists {
log.Fatalf("Environment variable %s not set", key)
}
return value
}
func updateTrustNetworkFilter() {
trustNetworkFilterMu.Lock()
defer trustNetworkFilterMu.Unlock()
nKeys := uint64(len(trustNetwork))
log.Println("🌐 updating trust network filter with", nKeys, "keys")
trustNetworkFilter = blobloom.NewOptimized(blobloom.Config{
Capacity: nKeys,
FPRate: 1e-4,
})
for _, trustedPubkey := range trustNetwork {
trustNetworkFilter.Add(xxhash.Sum64([]byte(trustedPubkey)))
}
}
func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) []string {
2024-09-06 11:16:40 -04:00
runTrustNetworkRefresh := func() {
2024-09-06 14:06:32 -04:00
timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
2024-09-06 11:16:40 -04:00
defer cancel()
filters := []nostr.Filter{{
Authors: []string{config.RelayPubkey},
Kinds: []int{nostr.KindContactList},
}}
log.Println("🔍 fetching owner's follows")
for ev := range pool.SubManyEose(timeoutCtx, seedRelays, filters) {
2024-09-06 11:16:40 -04:00
for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) {
appendPubkey(contact[1])
}
}
follows := make([]string, len(trustNetwork))
copy(follows, trustNetwork)
2024-09-06 11:16:40 -04:00
log.Println("🌐 building web of trust graph")
for i := 0; i < len(follows); i += 200 {
timeout, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
end := i + 200
if end > len(follows) {
end = len(follows)
}
2024-09-06 11:16:40 -04:00
filters = []nostr.Filter{{
Authors: follows[i:end],
Kinds: []int{nostr.KindContactList, nostr.KindRelayListMetadata, nostr.KindProfileMetadata},
2024-09-06 11:16:40 -04:00
}}
for ev := range pool.SubManyEose(timeout, seedRelays, filters) {
2024-09-06 11:16:40 -04:00
for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) {
if len(contact) > 1 {
appendPubkey(contact[1])
}
}
for _, relay := range ev.Event.Tags.GetAll([]string{"r"}) {
appendRelay(relay[1])
}
if ev.Event.Kind == nostr.KindProfileMetadata {
relay.AddEvent(ctx, ev.Event)
}
2024-09-06 11:16:40 -04:00
}
}
log.Println("🫂 network size:", len(trustNetwork))
log.Println("🔗 relays discovered:", len(relays))
}
runTrustNetworkRefresh()
updateTrustNetworkFilter()
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
for range ticker.C {
runTrustNetworkRefresh()
updateTrustNetworkFilter()
2024-09-06 11:16:40 -04:00
}
return trustNetwork
}
func appendRelay(relay string) {
mu.Lock()
defer mu.Unlock()
2024-09-06 13:24:33 -04:00
for _, r := range relays {
if r == relay {
return
2024-09-06 13:24:33 -04:00
}
}
relays = append(relays, relay)
2024-09-06 13:24:33 -04:00
}
2024-09-06 11:16:40 -04:00
func appendPubkey(pubkey string) {
mu.Lock()
defer mu.Unlock()
for _, pk := range trustNetwork {
if pk == pubkey {
return
}
}
if len(pubkey) != 64 {
return
}
2024-09-06 11:16:40 -04:00
trustNetwork = append(trustNetwork, 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)
defer cancel()
filters := []nostr.Filter{{
Kinds: []int{
nostr.KindArticle,
nostr.KindDeletion,
nostr.KindContactList,
nostr.KindEncryptedDirectMessage,
nostr.KindMuteList,
nostr.KindReaction,
nostr.KindRelayListMetadata,
nostr.KindRepost,
nostr.KindZapRequest,
nostr.KindZap,
nostr.KindTextNote,
},
}}
log.Println("📦 archiving trusted notes...")
var i int64
trustNetworkFilterMu.Lock()
for ev := range pool.SubMany(timeout, seedRelays, filters) {
if trustNetworkFilter.Has(xxhash.Sum64([]byte(ev.Event.PubKey))) {
if len(ev.Event.Tags) > 2000 {
continue
2024-09-06 11:16:40 -04:00
}
relay.AddEvent(ctx, ev.Event)
i++
2024-09-06 11:16:40 -04:00
}
}
trustNetworkFilterMu.Unlock()
fmt.Println("📦 archived", i, "trusted notes")
2024-09-06 11:16:40 -04:00
}