From 5135dd4874027c3dd0dfcaf61f38c7106c850465 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 11:16:40 -0400 Subject: [PATCH 01/31] init wot relay --- .env.example | 4 + .gitignore | 2 + README.md | 104 ++++++++++++++++++++++++++ go.mod | 36 +++++++++ go.sum | 71 ++++++++++++++++++ main.go | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 424 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e5c869c --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +RELAY_NAME="utxo WoT relay" +RELAY_PUBKEY="e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb" +RELAY_DESCRIPTION="Only notes in utxo WoT" +DB_PATH="db" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89ee3e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +db/ +.env \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9774c02 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# WOT Relay + +WOT Relay is a Nostr relay specialized for managing a web of trust based on trusted public keys. This relay is designed to run on localhost, with events filtered by a defined trust network. + +## Prerequisites + +- **Go**: Ensure you have Go installed on your system. You can download it from [here](https://golang.org/dl/). + +## Setup Instructions + +Follow these steps to get the WOT Relay running on your local machine: + +### 1. Clone the repository + +```bash +git clone https://github.com/bitvora/wot-relay.git +cd wot-relay +``` + +### 2. Copy `.env.example` to `.env` + +You'll need to create an `.env` file based on the example provided in the repository. + +```bash +cp .env.example .env +``` + +### 3. Set your environment variables + +Open the `.env` file and set the necessary environment variables. Example variables include: + +```bash +RELAY_NAME="YourRelayName" +RELAY_PUBKEY="YourPublicKey" +RELAY_DESCRIPTION="Your relay description" +DB_PATH="/path/to/your/database" +``` + +### 4. Build the project + +Run the following command to build the relay: + +```bash +go build +``` + +### 5. Create a Systemd Service (optional) + +To have the relay run as a service, create a systemd unit file. Here's an example: + +1. Create the file: + +```bash +sudo nano /etc/systemd/system/wot-relay.service +``` + +2. Add the following contents: + +```ini +[Unit] +Description=WOT Relay Service +After=network.target + +[Service] +ExecStart=/path/to/wot-relay +WorkingDirectory=/path/to/wot-relay +Restart=always +EnvironmentFile=/path/to/.env + +[Install] +WantedBy=multi-user.target +``` + +Replace `/path/to/` with the actual paths where you cloned the repository and stored the `.env` file. + +3. Reload systemd to recognize the new service: + +```bash +sudo systemctl daemon-reload +``` + +4. Start the service: + +```bash +sudo systemctl start wot-relay +``` + +5. (Optional) Enable the service to start on boot: + +```bash +sudo systemctl enable wot-relay +``` + +### 6. Access the relay + +Once everything is set up, the relay will be running on `localhost:3334`. + +```bash +http://localhost:3334 +``` + +## License + +This project is licensed under the MIT License. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..620210d --- /dev/null +++ b/go.mod @@ -0,0 +1,36 @@ +module github.com/bitvora/wot-relay + +go 1.23.0 + +toolchain go1.23.1 + +require ( + github.com/PowerDNS/lmdb-go v1.9.2 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/fasthttp/websocket v1.5.7 // indirect + github.com/fiatjaf/eventstore v0.8.1 // indirect + github.com/fiatjaf/khatru v0.8.0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.3.1 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/nbd-wtf/go-nostr v0.34.14 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/rs/cors v1.7.0 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect + github.com/tidwall/gjson v1.17.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.20.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3249bc3 --- /dev/null +++ b/go.sum @@ -0,0 +1,71 @@ +github.com/PowerDNS/lmdb-go v1.9.2 h1:Cmgerh9y3ZKBZGz1irxSShhfmFyRUh+Zdk4cZk7ZJvU= +github.com/PowerDNS/lmdb-go v1.9.2/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4= +github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU= +github.com/fiatjaf/eventstore v0.5.1 h1:tTh+JYP0RME51VY2QB2Gvtzj6QTaZAnSVhgZtrYqY2A= +github.com/fiatjaf/eventstore v0.5.1/go.mod h1:r5yCFmrVNT2b1xUOuMnDVS3xPGh97y8IgTcLyY2rYP8= +github.com/fiatjaf/eventstore v0.8.1 h1:51LchQNy0Hpb0YQHwqYR5pKBpfDs/KjySlWCbbz2pkc= +github.com/fiatjaf/eventstore v0.8.1/go.mod h1:bsp0Ibv0CIcVuFcoM2AEerMWmXRhF8uWXMf+dClhuow= +github.com/fiatjaf/khatru v0.8.0 h1:hofUi4qbSqkJiKD4rC9EyNdi9obzBvp3ykJOBxuu/h8= +github.com/fiatjaf/khatru v0.8.0/go.mod h1:jRmqbbIbEH+y0unt3wMUBwqY/btVussqx5SmBoGhXtg= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I= +github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU= +github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/nbd-wtf/go-nostr v0.34.14 h1:o4n2LkuAtdIjNYJ23sFbcx68UXLnji4j8hYR1Sd2wgI= +github.com/nbd-wtf/go-nostr v0.34.14/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs= +github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew= +github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..ba5577b --- /dev/null +++ b/main.go @@ -0,0 +1,207 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "sync" + "time" + + "github.com/fiatjaf/eventstore/lmdb" + "github.com/fiatjaf/khatru" + "github.com/joho/godotenv" + "github.com/nbd-wtf/go-nostr" +) + +type Config struct { + RelayName string + RelayPubkey string + RelayDescription string + DBPath string +} + +var pool *nostr.SimplePool +var relays []string +var config Config +var trustNetwork []string +var mu sync.Mutex + +func main() { + fmt.Println("starting") + relay := khatru.NewRelay() + ctx := context.Background() + + config = LoadConfig() + + relay.Info.Name = config.RelayName + relay.Info.PubKey = config.RelayPubkey + relay.Info.Description = config.RelayDescription + trustNetwork = append(trustNetwork, config.RelayPubkey) + + db := lmdb.LMDBBackend{ + Path: getEnv("DB_PATH"), + } + if err := db.Init(); err != nil { + panic(err) + } + + go refreshTrustNetwork() + go archiveTrustedNotes(relay) + + relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) + relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents) + relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) { + for _, pk := range trustNetwork { + if pk == event.PubKey { + return false, "" + } + } + return true, "you are not in the web of trust" + }) + + relays = []string{ + "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", + "wss://nostrarchives.com", + } + + pool = nostr.NewSimplePool(ctx) + + fmt.Println("running on :3334") + http.ListenAndServe(":3334", relay) +} + +func LoadConfig() Config { + err := godotenv.Load(".env") + if err != nil { + log.Fatalf("Error loading .env file") + } + + config := Config{ + RelayName: getEnv("RELAY_NAME"), + RelayPubkey: getEnv("RELAY_PUBKEY"), + RelayDescription: getEnv("RELAY_DESCRIPTION"), + DBPath: getEnv("DB_PATH"), + } + + 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 refreshTrustNetwork() []string { + ctx := context.Background() + ticker := time.NewTicker(1 * time.Minute) + for range ticker.C { + + timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + + filters := []nostr.Filter{{ + Authors: []string{config.RelayPubkey}, + Kinds: []int{nostr.KindContactList}, + }} + + for ev := range pool.SubManyEose(timeoutCtx, relays, filters) { + for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) { + appendPubkey(contact[1]) + } + } + + fmt.Println("trust network size:", len(trustNetwork)) + + chunks := make([][]string, 0) + for i := 0; i < len(trustNetwork); i += 100 { + end := i + 100 + if end > len(trustNetwork) { + end = len(trustNetwork) + } + chunks = append(chunks, trustNetwork[i:end]) + } + + for _, chunk := range chunks { + threeTimeoutCtx, tenCancel := context.WithTimeout(ctx, 3*time.Second) + defer tenCancel() + + filters = []nostr.Filter{{ + Authors: chunk, + Kinds: []int{nostr.KindContactList}, + }} + + for ev := range pool.SubManyEose(threeTimeoutCtx, relays, filters) { + for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) { + if len(contact) > 1 { + appendPubkey(contact[1]) + } else { + fmt.Println("Skipping malformed tag: ", contact) + } + } + } + } + + fmt.Println("trust network size:", len(trustNetwork)) + } + + return trustNetwork +} + +func appendPubkey(pubkey string) { + mu.Lock() + defer mu.Unlock() + + for _, pk := range trustNetwork { + if pk == pubkey { + return + } + } + trustNetwork = append(trustNetwork, pubkey) +} + +func archiveTrustedNotes(relay *khatru.Relay) { + ctx := context.Background() + 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, + }, + }} + + for ev := range pool.SubMany(ctx, relays, filters) { + for _, trustedPubkey := range trustNetwork { + if ev.Event.PubKey == trustedPubkey { + relay.AddEvent(ctx, ev.Event) + } + } + } +} From c75394a44ffc95ed0c09185550727419c59728a8 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 11:17:31 -0400 Subject: [PATCH 02/31] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9774c02..b7ad116 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # WOT Relay -WOT Relay is a Nostr relay specialized for managing a web of trust based on trusted public keys. This relay is designed to run on localhost, with events filtered by a defined trust network. +WOT Relay is a Nostr relay that saves all the notes that people you follow, and people they follow are posting. ## Prerequisites From b901d5f562b29c474e8a9792931a36bd85df8771 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 12:14:50 -0400 Subject: [PATCH 03/31] add text note --- .gitignore | 3 ++- main.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 89ee3e2..6f4c7e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ db/ -.env \ No newline at end of file +.env +wot-relay \ No newline at end of file diff --git a/main.go b/main.go index ba5577b..4cfb350 100644 --- a/main.go +++ b/main.go @@ -194,6 +194,7 @@ func archiveTrustedNotes(relay *khatru.Relay) { nostr.KindRepost, nostr.KindZapRequest, nostr.KindZap, + nostr.KindTextNote, }, }} From afd0f8f56693f2f3ad302ca4193c89afd9385bba Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 13:24:33 -0400 Subject: [PATCH 04/31] get profile metadata --- main.go | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 4cfb350..0d46fc5 100644 --- a/main.go +++ b/main.go @@ -47,7 +47,7 @@ func main() { panic(err) } - go refreshTrustNetwork() + go refreshTrustNetwork(relay) go archiveTrustedNotes(relay) relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) @@ -112,9 +112,9 @@ func getEnv(key string) string { return value } -func refreshTrustNetwork() []string { +func refreshTrustNetwork(relay *khatru.Relay) []string { ctx := context.Background() - ticker := time.NewTicker(1 * time.Minute) + ticker := time.NewTicker(1 * time.Hour) for range ticker.C { timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Second) @@ -131,8 +131,6 @@ func refreshTrustNetwork() []string { } } - fmt.Println("trust network size:", len(trustNetwork)) - chunks := make([][]string, 0) for i := 0; i < len(trustNetwork); i += 100 { end := i + 100 @@ -163,11 +161,38 @@ func refreshTrustNetwork() []string { } fmt.Println("trust network size:", len(trustNetwork)) + getTrustNetworkProfileMetadata(relay) } return trustNetwork } +func getTrustNetworkProfileMetadata(relay *khatru.Relay) { + ctx := context.Background() + + chunks := make([][]string, 0) + for i := 0; i < len(trustNetwork); i += 100 { + end := i + 100 + if end > len(trustNetwork) { + end = len(trustNetwork) + } + chunks = append(chunks, trustNetwork[i:end]) + } + + for _, chunk := range chunks { + timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + filters := []nostr.Filter{{ + Authors: chunk, + Kinds: []int{nostr.KindProfileMetadata}, + }} + + for ev := range pool.SubManyEose(timeoutCtx, relays, filters) { + relay.AddEvent(timeoutCtx, ev.Event) + } + } +} + func appendPubkey(pubkey string) { mu.Lock() defer mu.Unlock() From 61554badfce1343557cec0ccf3d1b79343c3db68 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 13:30:29 -0400 Subject: [PATCH 05/31] refresh every 10 mins --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 0d46fc5..ab32ba1 100644 --- a/main.go +++ b/main.go @@ -114,7 +114,7 @@ func getEnv(key string) string { func refreshTrustNetwork(relay *khatru.Relay) []string { ctx := context.Background() - ticker := time.NewTicker(1 * time.Hour) + ticker := time.NewTicker(10 * time.Minute) for range ticker.C { timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Second) From 338442006b811725ea8006f073fe26b5e9d6d3b1 Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode <123783602+HolgerHatGarKeineNode@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:49:30 +0000 Subject: [PATCH 06/31] Add Dockerfile and Docker Compose for wot-relay setup Introduced a Dockerfile to build the wot-relay application using the Golang image based on Debian Bookworm. Configured the working directory, cloned the repository, downloaded Go module dependencies, set environment variables, and built the Go application. Exposed port 3334 and set the command to run the executable. Added a Docker Compose file for streamlined service deployment, including environment variable configurations, volume binding for the database directory, and port mapping. --- Dockerfile | 27 +++++++++++++++++++++++++++ docker-compose.yml | 14 ++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..810ee65 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# Use Golang image based on Debian Bookworm +FROM golang:bookworm + +# Set the working directory within the container +WORKDIR /app + +# Clone the repository +RUN git clone https://github.com/bitvora/wot-relay . + +# Download Go module dependencies +RUN go mod download + +# Write the .env file +RUN touch .env && \ + echo "RELAY_NAME=${RELAY_NAME}" >> .env && \ + echo "RELAY_PUBKEY=${RELAY_PUBKEY}" >> .env && \ + echo "RELAY_DESCRIPTION=${RELAY_DESCRIPTION}" >> .env && \ + echo "DB_PATH=${DB_PATH}" >> .env + +# Build the Go application +RUN go build -o main . + +# Expose the port that the application will run on +EXPOSE 3334 + +# Set the command to run the executable +CMD ["./main"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5cf78f7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + wot-relay: + build: + context: . + dockerfile: Dockerfile + environment: + RELAY_NAME: "utxo WoT relay" + RELAY_PUBKEY: "e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb" + RELAY_DESCRIPTION: "Only notes in utxo WoT" + DB_PATH: "./db" + volumes: + - "./db:/app/db" + ports: + - "3334:3334" \ No newline at end of file From 3877f9456cfe1b6e75893c7e405bce04217dd3ff Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode <123783602+HolgerHatGarKeineNode@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:52:49 +0000 Subject: [PATCH 07/31] Update README.md Update README.md with Docker Compose startup instructions --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b7ad116..913b902 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,21 @@ sudo systemctl start wot-relay sudo systemctl enable wot-relay ``` -### 6. Access the relay +### 6. Start the Project with Docker Compose (optional) + +To start the project using Docker Compose, follow these steps: + +1. Ensure Docker and Docker Compose are installed on your system. +2. Navigate to the project directory. +3. Run the following command: + + ```sh + docker-compose up --build + ``` + +This will build the Docker image and start the `wot-relay` service as defined in the `docker-compose.yml` file. The application will be accessible on port 3334. + +### 7. Access the relay Once everything is set up, the relay will be running on `localhost:3334`. From aea9956bc0dd6655e6f34ca33594efa14866e867 Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode <123783602+HolgerHatGarKeineNode@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:54:55 +0000 Subject: [PATCH 08/31] Update README.md --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 913b902..706eb58 100644 --- a/README.md +++ b/README.md @@ -91,13 +91,23 @@ sudo systemctl start wot-relay sudo systemctl enable wot-relay ``` -### 6. Start the Project with Docker Compose (optional) +### 6. Start the Project with Docker Compose To start the project using Docker Compose, follow these steps: 1. Ensure Docker and Docker Compose are installed on your system. 2. Navigate to the project directory. -3. Run the following command: +3. Edit the `docker-compose.yml` file to update the environment variables as needed: + + ```yaml + environment: + RELAY_NAME: "utxo WoT relay" + RELAY_PUBKEY: "YOURPUBKEY" + RELAY_DESCRIPTION: "Only notes in utxo WoT" + DB_PATH: "./db" + ``` + +4. Run the following command: ```sh docker-compose up --build @@ -105,6 +115,7 @@ To start the project using Docker Compose, follow these steps: This will build the Docker image and start the `wot-relay` service as defined in the `docker-compose.yml` file. The application will be accessible on port 3334. + ### 7. Access the relay Once everything is set up, the relay will be running on `localhost:3334`. From 86bad8e69397804798d50db681e4891fb868307f Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode <123783602+HolgerHatGarKeineNode@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:55:21 +0000 Subject: [PATCH 09/31] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 706eb58..79c13ff 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ sudo systemctl start wot-relay sudo systemctl enable wot-relay ``` -### 6. Start the Project with Docker Compose +### 6. Start the Project with Docker Compose (optional) To start the project using Docker Compose, follow these steps: From e2b9fbc3237759456617d22ae4a32725ab728e87 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 14:06:32 -0400 Subject: [PATCH 10/31] fix race conditions --- main.go | 57 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/main.go b/main.go index ab32ba1..67d59aa 100644 --- a/main.go +++ b/main.go @@ -117,7 +117,7 @@ func refreshTrustNetwork(relay *khatru.Relay) []string { ticker := time.NewTicker(10 * time.Minute) for range ticker.C { - timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Second) + timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() filters := []nostr.Filter{{ @@ -206,28 +206,43 @@ func appendPubkey(pubkey string) { } func archiveTrustedNotes(relay *khatru.Relay) { - ctx := context.Background() - 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, - }, - }} + // Create a ticker to restart the function every minute + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() - for ev := range pool.SubMany(ctx, relays, filters) { - for _, trustedPubkey := range trustNetwork { - if ev.Event.PubKey == trustedPubkey { - relay.AddEvent(ctx, ev.Event) + for range ticker.C { + // Copy the current state of trustNetwork at the start of the function + mu.Lock() // Lock while copying the trustNetwork to avoid partial reads + localTrustNetwork := make([]string, len(trustNetwork)) + copy(localTrustNetwork, trustNetwork) + mu.Unlock() // Unlock immediately after copying + + ctx := context.Background() + 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, + }, + }} + + // Use the local copy of trustNetwork in the event loop + for ev := range pool.SubMany(ctx, relays, filters) { + for _, trustedPubkey := range localTrustNetwork { + if ev.Event.PubKey == trustedPubkey { + relay.AddEvent(ctx, ev.Event) + } } } + + fmt.Println("archiveTrustedNotes: finished one cycle, will restart in 1 minute") } } From cc54cb54648fed8401dcb8ab27095dbe2bc18c3f Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 16:26:00 -0400 Subject: [PATCH 11/31] fix contact list bug, mutex race conditions, refresh trust --- main.go | 85 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/main.go b/main.go index 67d59aa..b9cacfe 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,9 @@ type Config struct { DBPath string } -var pool *nostr.SimplePool +var archivePool *nostr.SimplePool +var fetchingPool *nostr.SimplePool + var relays []string var config Config var trustNetwork []string @@ -38,7 +40,7 @@ func main() { relay.Info.Name = config.RelayName relay.Info.PubKey = config.RelayPubkey relay.Info.Description = config.RelayDescription - trustNetwork = append(trustNetwork, config.RelayPubkey) + appendPubkey(config.RelayPubkey) db := lmdb.LMDBBackend{ Path: getEnv("DB_PATH"), @@ -47,13 +49,15 @@ func main() { panic(err) } - go refreshTrustNetwork(relay) - go archiveTrustedNotes(relay) + mu.Lock() + copiedTrustNetwork := make([]string, len(trustNetwork)) + copy(copiedTrustNetwork, trustNetwork) + mu.Unlock() relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents) relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) { - for _, pk := range trustNetwork { + for _, pk := range copiedTrustNetwork { if pk == event.PubKey { return false, "" } @@ -61,6 +65,7 @@ func main() { return true, "you are not in the web of trust" }) + mu.Lock() relays = []string{ "wss://nos.lol", "wss://nostr.mom", @@ -79,10 +84,11 @@ func main() { "wss://nostr21.com", "wss://nostrue.com", "wss://relay.siamstr.com", - "wss://nostrarchives.com", } + mu.Unlock() - pool = nostr.NewSimplePool(ctx) + go refreshTrustNetwork(relay, ctx) + go archiveTrustedNotes(relay, ctx) fmt.Println("running on :3334") http.ListenAndServe(":3334", relay) @@ -112,11 +118,11 @@ func getEnv(key string) string { return value } -func refreshTrustNetwork(relay *khatru.Relay) []string { - ctx := context.Background() - ticker := time.NewTicker(10 * time.Minute) - for range ticker.C { +func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) []string { + fetchingPool = nostr.NewSimplePool(ctx) + // Function to refresh the trust network + runTrustNetworkRefresh := func() { timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() @@ -125,7 +131,7 @@ func refreshTrustNetwork(relay *khatru.Relay) []string { Kinds: []int{nostr.KindContactList}, }} - for ev := range pool.SubManyEose(timeoutCtx, relays, filters) { + for ev := range fetchingPool.SubManyEose(timeoutCtx, relays, filters) { for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) { appendPubkey(contact[1]) } @@ -141,7 +147,7 @@ func refreshTrustNetwork(relay *khatru.Relay) []string { } for _, chunk := range chunks { - threeTimeoutCtx, tenCancel := context.WithTimeout(ctx, 3*time.Second) + threeTimeoutCtx, tenCancel := context.WithTimeout(ctx, 10*time.Second) defer tenCancel() filters = []nostr.Filter{{ @@ -149,7 +155,7 @@ func refreshTrustNetwork(relay *khatru.Relay) []string { Kinds: []int{nostr.KindContactList}, }} - for ev := range pool.SubManyEose(threeTimeoutCtx, relays, filters) { + for ev := range fetchingPool.SubManyEose(threeTimeoutCtx, relays, filters) { for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) { if len(contact) > 1 { appendPubkey(contact[1]) @@ -161,15 +167,22 @@ func refreshTrustNetwork(relay *khatru.Relay) []string { } fmt.Println("trust network size:", len(trustNetwork)) - getTrustNetworkProfileMetadata(relay) + getTrustNetworkProfileMetadata(relay, ctx) + } + + runTrustNetworkRefresh() + + ticker := time.NewTicker(10 * time.Minute) + defer ticker.Stop() + + for range ticker.C { + runTrustNetworkRefresh() } return trustNetwork } -func getTrustNetworkProfileMetadata(relay *khatru.Relay) { - ctx := context.Background() - +func getTrustNetworkProfileMetadata(relay *khatru.Relay, ctx context.Context) { chunks := make([][]string, 0) for i := 0; i < len(trustNetwork); i += 100 { end := i + 100 @@ -187,8 +200,8 @@ func getTrustNetworkProfileMetadata(relay *khatru.Relay) { Kinds: []int{nostr.KindProfileMetadata}, }} - for ev := range pool.SubManyEose(timeoutCtx, relays, filters) { - relay.AddEvent(timeoutCtx, ev.Event) + for ev := range fetchingPool.SubManyEose(timeoutCtx, relays, filters) { + relay.AddEvent(ctx, ev.Event) } } } @@ -205,19 +218,20 @@ func appendPubkey(pubkey string) { trustNetwork = append(trustNetwork, pubkey) } -func archiveTrustedNotes(relay *khatru.Relay) { - // Create a ticker to restart the function every minute +func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() - for range ticker.C { - // Copy the current state of trustNetwork at the start of the function - mu.Lock() // Lock while copying the trustNetwork to avoid partial reads - localTrustNetwork := make([]string, len(trustNetwork)) - copy(localTrustNetwork, trustNetwork) - mu.Unlock() // Unlock immediately after copying + archivePool = nostr.NewSimplePool(ctx) + + for range ticker.C { + mu.Lock() + trustNetworkCopy := make([]string, len(trustNetwork)) + copy(trustNetworkCopy, trustNetwork) + mu.Unlock() + ctxTimeout, cancel := context.WithTimeout(ctx, 1*time.Minute) + // Create a new context with timeout for each iteration of the loop - ctx := context.Background() filters := []nostr.Filter{{ Kinds: []int{ nostr.KindArticle, @@ -234,14 +248,21 @@ func archiveTrustedNotes(relay *khatru.Relay) { }, }} - // Use the local copy of trustNetwork in the event loop - for ev := range pool.SubMany(ctx, relays, filters) { - for _, trustedPubkey := range localTrustNetwork { + // Iterate over events from the pool and archive trusted notes + for ev := range archivePool.SubManyEose(ctxTimeout, relays, filters) { + for _, trustedPubkey := range trustNetworkCopy { if ev.Event.PubKey == trustedPubkey { + if ev.Event.Kind == nostr.KindContactList { + if len(ev.Event.Tags.GetAll([]string{"p"})) > 2000 { + fmt.Println("archiveTrustedNotes: skipping contact list with more than 2000 contacts. NoteID: ", ev.Event.ID) + continue + } + } relay.AddEvent(ctx, ev.Event) } } } + cancel() fmt.Println("archiveTrustedNotes: finished one cycle, will restart in 1 minute") } From b762c1c974219b097dae50dfd0ae42d4af25981f Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 16:33:04 -0400 Subject: [PATCH 12/31] increase timeout length --- main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index b9cacfe..af93da1 100644 --- a/main.go +++ b/main.go @@ -219,7 +219,7 @@ func appendPubkey(pubkey string) { } func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { - ticker := time.NewTicker(1 * time.Minute) + ticker := time.NewTicker(10 * time.Minute) defer ticker.Stop() archivePool = nostr.NewSimplePool(ctx) @@ -229,7 +229,7 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { trustNetworkCopy := make([]string, len(trustNetwork)) copy(trustNetworkCopy, trustNetwork) mu.Unlock() - ctxTimeout, cancel := context.WithTimeout(ctx, 1*time.Minute) + ctxTimeout, cancel := context.WithTimeout(ctx, 9*time.Minute) // Create a new context with timeout for each iteration of the loop filters := []nostr.Filter{{ @@ -264,6 +264,6 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { } cancel() - fmt.Println("archiveTrustedNotes: finished one cycle, will restart in 1 minute") + fmt.Println("archiveTrustedNotes: finished one cycle, will restart in 10 minutes") } } From 797c3d89b4f5f712ea962c68997cb4e104c06e09 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 17:15:39 -0400 Subject: [PATCH 13/31] fix archiving --- main.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index af93da1..4b06fa5 100644 --- a/main.go +++ b/main.go @@ -219,18 +219,17 @@ func appendPubkey(pubkey string) { } func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { - ticker := time.NewTicker(10 * time.Minute) + ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() archivePool = nostr.NewSimplePool(ctx) for range ticker.C { + since := nostr.Timestamp(time.Now().Unix()) mu.Lock() trustNetworkCopy := make([]string, len(trustNetwork)) copy(trustNetworkCopy, trustNetwork) mu.Unlock() - ctxTimeout, cancel := context.WithTimeout(ctx, 9*time.Minute) - // Create a new context with timeout for each iteration of the loop filters := []nostr.Filter{{ Kinds: []int{ @@ -246,10 +245,11 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { nostr.KindZap, nostr.KindTextNote, }, + Since: &since, }} - // Iterate over events from the pool and archive trusted notes - for ev := range archivePool.SubManyEose(ctxTimeout, relays, filters) { + timeout, cancel := context.WithTimeout(ctx, 1*time.Minute) + for ev := range archivePool.SubManyEose(timeout, relays, filters) { for _, trustedPubkey := range trustNetworkCopy { if ev.Event.PubKey == trustedPubkey { if ev.Event.Kind == nostr.KindContactList { @@ -263,7 +263,5 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { } } cancel() - - fmt.Println("archiveTrustedNotes: finished one cycle, will restart in 10 minutes") } } From b68cdeffbaf2f4eb98cc788e815dc37c1bf5d16e Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 17:39:39 -0400 Subject: [PATCH 14/31] fix archiving --- main.go | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/main.go b/main.go index 4b06fa5..ac3868d 100644 --- a/main.go +++ b/main.go @@ -49,15 +49,10 @@ func main() { panic(err) } - mu.Lock() - copiedTrustNetwork := make([]string, len(trustNetwork)) - copy(copiedTrustNetwork, trustNetwork) - mu.Unlock() - relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents) relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) { - for _, pk := range copiedTrustNetwork { + for _, pk := range trustNetwork { if pk == event.PubKey { return false, "" } @@ -223,14 +218,8 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { defer ticker.Stop() archivePool = nostr.NewSimplePool(ctx) - for range ticker.C { - since := nostr.Timestamp(time.Now().Unix()) - mu.Lock() - trustNetworkCopy := make([]string, len(trustNetwork)) - copy(trustNetworkCopy, trustNetwork) - mu.Unlock() - + timeout, cancel := context.WithTimeout(ctx, 58*time.Second) filters := []nostr.Filter{{ Kinds: []int{ nostr.KindArticle, @@ -245,12 +234,10 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { nostr.KindZap, nostr.KindTextNote, }, - Since: &since, }} - timeout, cancel := context.WithTimeout(ctx, 1*time.Minute) for ev := range archivePool.SubManyEose(timeout, relays, filters) { - for _, trustedPubkey := range trustNetworkCopy { + for _, trustedPubkey := range trustNetwork { if ev.Event.PubKey == trustedPubkey { if ev.Event.Kind == nostr.KindContactList { if len(ev.Event.Tags.GetAll([]string{"p"})) > 2000 { @@ -259,6 +246,7 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { } } relay.AddEvent(ctx, ev.Event) + fmt.Println("archived trusted note: ", ev.Event.ID) } } } From dc631d17b6761bcc17a157c821ded05236df129a Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 19:25:40 -0400 Subject: [PATCH 15/31] home page and favico --- main.go | 25 ++++++++ templates/index.html | 64 ++++++++++++++++++++ templates/static/android-chrome-192x192.png | Bin 0 -> 6977 bytes templates/static/android-chrome-256x256.png | Bin 0 -> 10135 bytes templates/static/apple-touch-icon.png | Bin 0 -> 6510 bytes templates/static/browserconfig.xml | 9 +++ templates/static/favicon-16x16.png | Bin 0 -> 846 bytes templates/static/favicon-32x32.png | Bin 0 -> 1251 bytes templates/static/favicon.ico | Bin 0 -> 15086 bytes templates/static/mstile-150x150.png | Bin 0 -> 4970 bytes templates/static/safari-pinned-tab.svg | 15 +++++ 11 files changed, 113 insertions(+) create mode 100644 templates/index.html create mode 100644 templates/static/android-chrome-192x192.png create mode 100644 templates/static/android-chrome-256x256.png create mode 100644 templates/static/apple-touch-icon.png create mode 100644 templates/static/browserconfig.xml create mode 100644 templates/static/favicon-16x16.png create mode 100644 templates/static/favicon-32x32.png create mode 100644 templates/static/favicon.ico create mode 100644 templates/static/mstile-150x150.png create mode 100644 templates/static/safari-pinned-tab.svg diff --git a/main.go b/main.go index ac3868d..aaa7c1f 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "html/template" "log" "net/http" "os" @@ -20,6 +21,7 @@ type Config struct { RelayPubkey string RelayDescription string DBPath string + RelayURL string } var archivePool *nostr.SimplePool @@ -85,6 +87,28 @@ func main() { go refreshTrustNetwork(relay, ctx) go archiveTrustedNotes(relay, ctx) + 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) + } + }) + + mux.Handle("/favicon.ico", http.StripPrefix("/", http.FileServer(http.Dir("/mnt/dev/bitvora/wot-relay/templates/static")))) + fmt.Println("running on :3334") http.ListenAndServe(":3334", relay) } @@ -100,6 +124,7 @@ func LoadConfig() Config { RelayPubkey: getEnv("RELAY_PUBKEY"), RelayDescription: getEnv("RELAY_DESCRIPTION"), DBPath: getEnv("DB_PATH"), + RelayURL: getEnv("RELAY_URL"), } return config diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..c61c143 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,64 @@ + + + + + + + + {{.RelayName}} + + + + +
+ +
+ +

+ {{.RelayName}} +

+ + +

+ {{.RelayDescription}} +

+ + + + {{.RelayURL}} + +
+
+ + + + + diff --git a/templates/static/android-chrome-192x192.png b/templates/static/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..8f148a79eba6e7fbc7dfb8c6104a5a03832fa4c7 GIT binary patch literal 6977 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 literal 0 HcmV?d00001 diff --git a/templates/static/apple-touch-icon.png b/templates/static/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a14d17c5df25a6bd720048a232e8dd5fa0812292 GIT binary patch literal 6510 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 Fe*q!noNWLA literal 0 HcmV?d00001 diff --git a/templates/static/browserconfig.xml b/templates/static/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/templates/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/templates/static/favicon-16x16.png b/templates/static/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..4d5b6ed558e41a82eed3c704d7e74209fbb240f9 GIT binary patch literal 846 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>`Z@Ck8ERcVHxWW~lL zg~nv1reuZ26s4vt?e^~IMU&H3w1>>g)M{(+nmM<4!_xX~)3R2V*-lJRZmM#e^7`@n ze}Dgdc=`F%mMia`fBgIR-~a#r|Ni;=^wz8C*{iFar@nvr>Hoj~=XTy$QnzhJ&YF3p z8;`8M@blZRw@*Jb`Ofhq^JUuxZ^CCJui>vU|Jeq?sxx^p|PZ<=`U%lmH)-ZKkL zdM@m_xwv*~Z_MJ;Td&?e^XS;Ri|OjE)3a7*>vrTDbr)Oom00ypNL+ew`PnT~4_CWP zy?o&I%Li|t-+lA>-PenIZ)Iq-p5J|AMf1*!`))nD`n5E&EqTJl*J#$*-s+NE`sjAIvs%pR>W?(7_@(b>o zX{R$y63Amr@^*KTDouZ41mv)nc>21szu;sOXO(I{vP}ahwB6IiF+}2Wa)JZ9kB`q8 zoijdbbaZmwIG70sG&E1{p4~pZKi(mLL+5~=j*^y|o}#9zt}?gagvsFnAwgk*p_7BI zC$Ms7WM#g7@#>|tho__yYfsNiU!SvQbamFQQH)Z_y)h%_?HgH{xfKdbLFd-I(OZ}I zPHtXY0As$sn7pRKhgQxXKiOFpadfb3WLIW!a$0)m&`sqfOO`HG+IzsSe^ckPj!w~z zZs899gr>^A&bh1{5siN7F3b#yC!`|dSDVNIy`Wm+8c~vxSdwa$T$Bo=7>o>zOmqz_ zbq&lyjEt=eOs$N}v<(cb3=G_+2dqZXkei>9nO2EggZZf!;y?}IRUr{2L5bxG1x5L3 znK`KnC6xuK3Yi5Z$qWn?a~^-<;V2B#&^YCP`i$q(AO>b-ZoOn~VP#?O$s)|c3N8&M xhf|o9H-{*kzH#NmkuyhRj}VUz#> literal 0 HcmV?d00001 diff --git a/templates/static/favicon-32x32.png b/templates/static/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..2f293bf8f9aad5f7dc43e8e7f2634bfb26786acb GIT binary patch literal 1251 zcmZ`%do&Yz9REp0RHqI%-EOBg>){c0R-V1ku(e3DO1C;^_Hq}l$x?*288#(qL^U=& z+?DbOEixOH$2Pj3^#>qs8vqP80CR>GLkqw;H~^z~ z06cO5u%hv+kL&@!=w09;oDX?R(k}r^B0^b2D3fT-B3ZMjb}8YG+^`*-(Cx`#4xGF3 zm6h2I6){ltX5WbmR^-|jtQu?DG9zeCP$|i z7Z>|p4J94E{fJwm?HkwW^xwYBHkG_i^vk(~%AFef`gLMPx1by7(z1{6^?Lo6Pt(0G zH7ape-9N380!71vwsu+1m}XM1)4x;nN4sUTK6}H!+>CJIhHhiWdh*J7^|RC8i#X3` zroMBI3J9ASBv?`+D(7CpQ+6PqvW;CMZ29o7hPLy{gmxRWfLHbkj;#6C$75vV_Bw5`Lr|2IsL3ldL-iVx!sxh=gLYmq(kpUqus6+GQ@8h z`!wAn{hh-j>dz%tYg2;q3u4RXe=G=Mo)95Ss$J@9Y0tA;jU(#uP@80y|E=D(!3cQT zm6LaV%+Ar>Gk9dfste;olR05U7Y}6ThCli|{3Xsi>&BU)$Co7!Q>rDxmKs5eM5quY zRaIm)RNZcRao3^$=ZDa-0YQJf8Fq}ir#oukMg)u>5-Ja0wa!3H=xBc*6YYxSR?9Y| z%AeX8WF8vng>zG8w}%GRd#+iI?=Ww%6>8spI#CKGL~=wnGar7tI3aNzleq3!?8K~p zMQSF+OEr|?syr11#zVjGw?;tLS~2dN0`4nX0idc!?guKU`-Hd;`_5FBY)0yhm|k_z zf{^~TlTLW+yKpr9P&j{iH1{xyeaQ=pINAAEQ0dNUoUiFu>d5amPrP-nisd&uc`m`* z?6R|!Ti#5=1Ph&#QiE5jVKHkiHx0;W z-s=W&_N&|492m9FEKOREn|2pR%IhAlSn}y~nwvFTa z3FSR~`*D_@y#nVLzuJI;=y6*PggY2S^FwWt+Rwh+j{)zuT2E&-yAGhjhO=ETrwtd| zO7{t-lS1fZ58_#}0RRGy*yR9scYwR#5Ia5K&K?LCdpO(!4)@IoEc(R|5lNy_V*lUZ zs?b^)49DYe!E|iMc?j)nB!wDIhR|baWC)c;CjxMRKRC70H0qd*jpDFUg&x8JW0<)k z(cA)Nf#F#iTR@PET@55-SUC=&?CxlmtK=KyCQ1fMc+e4yTCJr|c{^ttMgX)=0J7K{ G&-xEH+EeoY literal 0 HcmV?d00001 diff --git a/templates/static/favicon.ico b/templates/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6da5dc08df9fcb70b88cc55f9e4514b33f6903e0 GIT binary patch literal 15086 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 OGlqKibg8fhQU3vk-X-V& literal 0 HcmV?d00001 diff --git a/templates/static/safari-pinned-tab.svg b/templates/static/safari-pinned-tab.svg new file mode 100644 index 0000000..4176fd1 --- /dev/null +++ b/templates/static/safari-pinned-tab.svg @@ -0,0 +1,15 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + From f3ed790a96e5bcfd59f968abc746d08158f8e19a Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 19:26:42 -0400 Subject: [PATCH 16/31] fix example and path --- .env.example | 4 +++- main.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index e5c869c..23fd632 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,6 @@ RELAY_NAME="utxo WoT relay" RELAY_PUBKEY="e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb" RELAY_DESCRIPTION="Only notes in utxo WoT" -DB_PATH="db" \ No newline at end of file +RELAY_URL="wss://wot.utxo.one" +DB_PATH="db" +INDEX_PATH="templates/index.html" \ No newline at end of file diff --git a/main.go b/main.go index aaa7c1f..01429b7 100644 --- a/main.go +++ b/main.go @@ -107,7 +107,7 @@ func main() { } }) - mux.Handle("/favicon.ico", http.StripPrefix("/", http.FileServer(http.Dir("/mnt/dev/bitvora/wot-relay/templates/static")))) + mux.Handle("/favicon.ico", http.StripPrefix("/", http.FileServer(http.Dir("templates/static")))) fmt.Println("running on :3334") http.ListenAndServe(":3334", relay) From bd451386dc53892237b012132796d2cf6f5aed1a Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 19:33:29 -0400 Subject: [PATCH 17/31] absolute paths --- main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 01429b7..4db5ed2 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,8 @@ type Config struct { RelayDescription string DBPath string RelayURL string + IndexPath string + StaticPath string } var archivePool *nostr.SimplePool @@ -107,7 +109,7 @@ func main() { } }) - mux.Handle("/favicon.ico", http.StripPrefix("/", http.FileServer(http.Dir("templates/static")))) + mux.Handle("/favicon.ico", http.StripPrefix("/", http.FileServer(http.Dir(config.StaticPath)))) fmt.Println("running on :3334") http.ListenAndServe(":3334", relay) @@ -125,6 +127,8 @@ func LoadConfig() Config { RelayDescription: getEnv("RELAY_DESCRIPTION"), DBPath: getEnv("DB_PATH"), RelayURL: getEnv("RELAY_URL"), + IndexPath: getEnv("INDEX_PATH"), + StaticPath: getEnv("STATIC_PATH"), } return config From e47aa19fa4a94a288f41ff6f6ddd9aee972d36ca Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Fri, 6 Sep 2024 19:38:39 -0400 Subject: [PATCH 18/31] why crash --- main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 4db5ed2..2183bab 100644 --- a/main.go +++ b/main.go @@ -112,7 +112,10 @@ func main() { mux.Handle("/favicon.ico", http.StripPrefix("/", http.FileServer(http.Dir(config.StaticPath)))) fmt.Println("running on :3334") - http.ListenAndServe(":3334", relay) + err := http.ListenAndServe(":3334", relay) + if err != nil { + log.Fatal(err) + } } func LoadConfig() Config { From e38dcc71ba4ed53d3e62ea112e9fea11d8118406 Mon Sep 17 00:00:00 2001 From: fiatjaf_ Date: Sat, 7 Sep 2024 07:10:01 -0300 Subject: [PATCH 19/31] don't fail when a `.env` file is not present still try to read it but let people use other ways to set up their environment variables. --- main.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/main.go b/main.go index 2183bab..7129692 100644 --- a/main.go +++ b/main.go @@ -119,10 +119,7 @@ func main() { } func LoadConfig() Config { - err := godotenv.Load(".env") - if err != nil { - log.Fatalf("Error loading .env file") - } + godotenv.Load(".env") config := Config{ RelayName: getEnv("RELAY_NAME"), From ca8dde54f1eb553029323a14d1478e5f962bab8a Mon Sep 17 00:00:00 2001 From: fsociety Date: Sat, 7 Sep 2024 15:57:21 +0200 Subject: [PATCH 20/31] feat: update Dockerfile, docker-compose and README for better Docker usage This commit updates the Dockerfile to use a more docker-friendly approach. Instead of cloning the repository inside the Dockerfile, it now copies the files from the host. It also sets some fixed environment variables and uses a .env file for the rest. The docker-compose.yml file now uses an .env file for more convenient environment variable management. It also maps volumes to the host, allowing you to change the paths of the `db` folder and `templates` folder. The README.md has been updated with new instructions reflecting these changes. It now includes instructions for running the Docker container in the foreground or background, and for updating the relay. --- .env.example | 3 ++- Dockerfile | 58 +++++++++++++++++++++++++--------------------- README.md | 34 ++++++++++++++++++--------- docker-compose.yml | 28 +++++++++++----------- 4 files changed, 70 insertions(+), 53 deletions(-) diff --git a/.env.example b/.env.example index 23fd632..6eeee36 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,5 @@ RELAY_PUBKEY="e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb" RELAY_DESCRIPTION="Only notes in utxo WoT" RELAY_URL="wss://wot.utxo.one" DB_PATH="db" -INDEX_PATH="templates/index.html" \ No newline at end of file +INDEX_PATH="templates/index.html" +STATIC_PATH="templates/static" diff --git a/Dockerfile b/Dockerfile index 810ee65..4c7e391 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,31 @@ -# Use Golang image based on Debian Bookworm -FROM golang:bookworm - -# Set the working directory within the container -WORKDIR /app - -# Clone the repository -RUN git clone https://github.com/bitvora/wot-relay . - -# Download Go module dependencies -RUN go mod download - -# Write the .env file -RUN touch .env && \ - echo "RELAY_NAME=${RELAY_NAME}" >> .env && \ - echo "RELAY_PUBKEY=${RELAY_PUBKEY}" >> .env && \ - echo "RELAY_DESCRIPTION=${RELAY_DESCRIPTION}" >> .env && \ - echo "DB_PATH=${DB_PATH}" >> .env - -# Build the Go application -RUN go build -o main . - -# Expose the port that the application will run on -EXPOSE 3334 - -# Set the command to run the executable -CMD ["./main"] +# Use Golang image based on Debian Bookworm +FROM golang:bookworm + +# Set the working directory within the container +WORKDIR /app + +# Copy go.mod and go.sum files +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +# Copy the rest of the application source code +COPY . . + +# Set fixed environment variables +ENV DB_PATH="db" +ENV INDEX_PATH="templates/index.html" +ENV STATIC_PATH="templates/static" + +# touch a .env (https://github.com/bitvora/wot-relay/pull/4) +RUN touch .env + +# Build the Go application +RUN go build -o main . + +# Expose the port that the application will run on +EXPOSE 3334 + +# Set the command to run the executable +CMD ["./main"] diff --git a/README.md b/README.md index 79c13ff..26359fa 100644 --- a/README.md +++ b/README.md @@ -97,25 +97,37 @@ To start the project using Docker Compose, follow these steps: 1. Ensure Docker and Docker Compose are installed on your system. 2. Navigate to the project directory. -3. Edit the `docker-compose.yml` file to update the environment variables as needed: +3. Ensure the `.env` file is present in the project directory and has the necessary environment variables set. +4. You can also change the paths of the `db` folder and `templates` folder in the `docker-compose.yml` file. - ```yaml - environment: - RELAY_NAME: "utxo WoT relay" - RELAY_PUBKEY: "YOURPUBKEY" - RELAY_DESCRIPTION: "Only notes in utxo WoT" - DB_PATH: "./db" - ``` + ```yaml + volumes: + - "./db:/app/db" # only change the left side before the colon + - "./templates/index.html:/app/templates/index.html" # only change the left side before the colon + - "./templates/static:/app/templates/static" # only change the left side before the colon + ``` -4. Run the following command: +5. Run the following command: ```sh - docker-compose up --build + # in foreground + docker compose up --build + # in background + docker compose up --build -d + ``` +6. For updating the relay, run the following command: + + ```sh + git pull + docker compose build --no-cache + # in foreground + docker compose up + # in background + docker compose up -d ``` This will build the Docker image and start the `wot-relay` service as defined in the `docker-compose.yml` file. The application will be accessible on port 3334. - ### 7. Access the relay Once everything is set up, the relay will be running on `localhost:3334`. diff --git a/docker-compose.yml b/docker-compose.yml index 5cf78f7..5b03ad6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,14 @@ -services: - wot-relay: - build: - context: . - dockerfile: Dockerfile - environment: - RELAY_NAME: "utxo WoT relay" - RELAY_PUBKEY: "e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb" - RELAY_DESCRIPTION: "Only notes in utxo WoT" - DB_PATH: "./db" - volumes: - - "./db:/app/db" - ports: - - "3334:3334" \ No newline at end of file +services: + wot-relay: + container_name: wot-relay + build: + context: . + dockerfile: Dockerfile + env_file: + - .env + volumes: + - "./db:/app/db" # only change the left side before the colon + - "./templates/index.html:/app/templates/index.html" # only change the left side before the colon + - "./templates/static:/app/templates/static" # only change the left side before the colon + ports: + - "3334:3334" From 8458b2a10fa9bde993911aaf95255a1abb95e9c1 Mon Sep 17 00:00:00 2001 From: fsociety Date: Sat, 7 Sep 2024 16:08:53 +0200 Subject: [PATCH 21/31] feat: add .dockerignore file to wot-relay repository --- .dockerignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..885eac2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +db +templates From 6372f55a7bce2ad2ce569efb3db5c14ad3b75e08 Mon Sep 17 00:00:00 2001 From: fsociety Date: Sat, 7 Sep 2024 16:37:58 +0200 Subject: [PATCH 22/31] feat: add Tor support for relay service Add Tor support for the relay service in the docker-compose file. Also, rename the service from `wot-relay` to `relay`. Add a new `torrc` configuration file and a `.gitignore` file in the `tor/data` directory to prevent sensitive data from being tracked. Also, create a new `docker-compose.tor.yml` file for Tor-specific configurations. --- docker-compose.tor.yml | 26 ++++++++++++++++++++++++++ docker-compose.yml | 2 +- tor/data/.gitignore | 2 ++ tor/torrc | 2 ++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 docker-compose.tor.yml create mode 100644 tor/data/.gitignore create mode 100644 tor/torrc diff --git a/docker-compose.tor.yml b/docker-compose.tor.yml new file mode 100644 index 0000000..15d5760 --- /dev/null +++ b/docker-compose.tor.yml @@ -0,0 +1,26 @@ +services: + relay: + container_name: wot-relay + build: + context: . + dockerfile: Dockerfile + env_file: + - .env + volumes: + - "./db:/app/db" + - "./templates/index.html:/app/templates/index.html" + - "./templates/static:/app/templates/static" + ports: + - "3334" # disable clearnet access + #- "3334:3334" # enable clearnet access + + tor: + image: lncm/tor:0.4.7.9@sha256:86c2fe9d9099e6376798979110b8b9a3ee5d8adec27289ac4a5ee892514ffe92 + container_name: wot-relay-tor + depends_on: + - relay + volumes: + - ./tor/torrc:/etc/tor/torrc + - ./tor/data:/var/lib/tor + restart: on-failure + stop_grace_period: 10m30s diff --git a/docker-compose.yml b/docker-compose.yml index 5b03ad6..13ca973 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ services: - wot-relay: + relay: container_name: wot-relay build: context: . diff --git a/tor/data/.gitignore b/tor/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tor/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tor/torrc b/tor/torrc new file mode 100644 index 0000000..e8e0702 --- /dev/null +++ b/tor/torrc @@ -0,0 +1,2 @@ +HiddenServiceDir /var/lib/tor/relay +HiddenServicePort 80 relay:3334 From 4dc6cab61880595f0f8d3a4ad6bdd572e4f740c0 Mon Sep 17 00:00:00 2001 From: fsociety Date: Sat, 7 Sep 2024 16:53:07 +0200 Subject: [PATCH 23/31] feat: add optional Tor support with clearnet toggle Added support for optionally running the wot-relay service as a Tor hidden service. Updated the docker-compose.tor.yml file to conditionally enable clearnet access, based on the `ENABLE_CLEARNET` environment variable. Updated the README.md file with instructions on how to use this feature. --- .env.example | 1 + README.md | 17 ++++++++++++++++- docker-compose.tor.yml | 4 ++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 6eeee36..cb2991b 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,4 @@ RELAY_URL="wss://wot.utxo.one" DB_PATH="db" INDEX_PATH="templates/index.html" STATIC_PATH="templates/static" +ENABLE_CLEARNET=false diff --git a/README.md b/README.md index 26359fa..772e122 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,22 @@ To start the project using Docker Compose, follow these steps: This will build the Docker image and start the `wot-relay` service as defined in the `docker-compose.yml` file. The application will be accessible on port 3334. -### 7. Access the relay +### 7. Hidden Service with Tor (optional) + +Same as the step 6, but with the following command: + +```sh +# in foreground +docker compose -f docker-compose.tor.yml up --build +# in background +docker compose -f docker-compose.tor.yml up --build -d +``` + +You can disable or enable clearnet access by changing `ENABLE_CLEARNET=false` or `ENABLE_CLEARNET=true` in the `.env` file. + +You can find the onion address here: `tor/data/relay/hostname` + +### 8. Access the relay Once everything is set up, the relay will be running on `localhost:3334`. diff --git a/docker-compose.tor.yml b/docker-compose.tor.yml index 15d5760..6bb6b4d 100644 --- a/docker-compose.tor.yml +++ b/docker-compose.tor.yml @@ -11,8 +11,8 @@ services: - "./templates/index.html:/app/templates/index.html" - "./templates/static:/app/templates/static" ports: - - "3334" # disable clearnet access - #- "3334:3334" # enable clearnet access + - "3334" # default port + - ${ENABLE_CLEARNET:+3334:3334} tor: image: lncm/tor:0.4.7.9@sha256:86c2fe9d9099e6376798979110b8b9a3ee5d8adec27289ac4a5ee892514ffe92 From 3698089a8f77e9d71fe527aabd46c965dce2f36d Mon Sep 17 00:00:00 2001 From: fsociety Date: Sat, 7 Sep 2024 17:05:30 +0200 Subject: [PATCH 24/31] feat: remove clearnet option from application configuration This commit removes the ENABLE_CLEARNET option from the .env.example file and the docker-compose.tor.yml file. The application will now only use the default port 3334. --- .env.example | 1 - docker-compose.tor.yml | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.env.example b/.env.example index cb2991b..6eeee36 100644 --- a/.env.example +++ b/.env.example @@ -5,4 +5,3 @@ RELAY_URL="wss://wot.utxo.one" DB_PATH="db" INDEX_PATH="templates/index.html" STATIC_PATH="templates/static" -ENABLE_CLEARNET=false diff --git a/docker-compose.tor.yml b/docker-compose.tor.yml index 6bb6b4d..9f03092 100644 --- a/docker-compose.tor.yml +++ b/docker-compose.tor.yml @@ -11,8 +11,7 @@ services: - "./templates/index.html:/app/templates/index.html" - "./templates/static:/app/templates/static" ports: - - "3334" # default port - - ${ENABLE_CLEARNET:+3334:3334} + - "3334" tor: image: lncm/tor:0.4.7.9@sha256:86c2fe9d9099e6376798979110b8b9a3ee5d8adec27289ac4a5ee892514ffe92 From 3f38d91de6aacd5bdb16b47b0f8a768f343a3b60 Mon Sep 17 00:00:00 2001 From: fsociety Date: Sat, 7 Sep 2024 17:14:06 +0200 Subject: [PATCH 25/31] docs: remove ENABLE_CLEARNET option from README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 772e122..91244c6 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,6 @@ docker compose -f docker-compose.tor.yml up --build docker compose -f docker-compose.tor.yml up --build -d ``` -You can disable or enable clearnet access by changing `ENABLE_CLEARNET=false` or `ENABLE_CLEARNET=true` in the `.env` file. - You can find the onion address here: `tor/data/relay/hostname` ### 8. Access the relay From 3f3e42267ebd3b0959857e67836ff0dd5168247a Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Sat, 7 Sep 2024 13:21:22 -0400 Subject: [PATCH 26/31] bloom filters, reduced logging --- go.mod | 3 +++ go.sum | 18 ++++++++++++++++++ main.go | 27 +++++++++++++++++---------- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 620210d..82ad671 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/andybalholm/brotli v1.0.5 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/fasthttp/websocket v1.5.7 // indirect @@ -17,6 +19,7 @@ require ( github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.3.1 // indirect + github.com/greatroar/blobloom v0.8.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.8 // indirect diff --git a/go.sum b/go.sum index 3249bc3..bcae549 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PowerDNS/lmdb-go v1.9.2 h1:Cmgerh9y3ZKBZGz1irxSShhfmFyRUh+Zdk4cZk7ZJvU= github.com/PowerDNS/lmdb-go v1.9.2/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= @@ -6,6 +7,12 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= @@ -26,6 +33,8 @@ github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I= github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU= github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/greatroar/blobloom v0.8.0 h1:I9RlEkfqK9/6f1v9mFmDYegDQ/x0mISCpiNpAm23Pt4= +github.com/greatroar/blobloom v0.8.0/go.mod h1:mjMJ1hh1wjGVfr93QIHJ6FfDNVrA0IELv8OvMHJxHKs= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -36,6 +45,7 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/nbd-wtf/go-nostr v0.34.14 h1:o4n2LkuAtdIjNYJ23sFbcx68UXLnji4j8hYR1Sd2wgI= github.com/nbd-wtf/go-nostr v0.34.14/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew= github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= @@ -44,6 +54,11 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= @@ -69,3 +84,6 @@ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 2183bab..b81f92d 100644 --- a/main.go +++ b/main.go @@ -10,8 +10,10 @@ import ( "sync" "time" + "github.com/cespare/xxhash" "github.com/fiatjaf/eventstore/lmdb" "github.com/fiatjaf/khatru" + "github.com/greatroar/blobloom" "github.com/joho/godotenv" "github.com/nbd-wtf/go-nostr" ) @@ -268,18 +270,23 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { }, }} + nKeys := uint64(len(trustNetwork)) + bloomFilter := blobloom.NewOptimized(blobloom.Config{ + Capacity: nKeys, + FPRate: 1e-4, + }) + for _, trustedPubkey := range trustNetwork { + bloomFilter.Add(xxhash.Sum64([]byte(trustedPubkey))) + } + for ev := range archivePool.SubManyEose(timeout, relays, filters) { - for _, trustedPubkey := range trustNetwork { - if ev.Event.PubKey == trustedPubkey { - if ev.Event.Kind == nostr.KindContactList { - if len(ev.Event.Tags.GetAll([]string{"p"})) > 2000 { - fmt.Println("archiveTrustedNotes: skipping contact list with more than 2000 contacts. NoteID: ", ev.Event.ID) - continue - } - } - relay.AddEvent(ctx, ev.Event) - fmt.Println("archived trusted note: ", ev.Event.ID) + + if bloomFilter.Has(xxhash.Sum64([]byte(ev.Event.PubKey))) { + if len(ev.Event.Tags) > 2000 { + fmt.Println("skiping note with over 2000 tags") + continue } + relay.AddEvent(ctx, ev.Event) } } cancel() From a385e86bad77269b1655e9ce5d2c402efb4f56e0 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Sat, 7 Sep 2024 13:29:56 -0400 Subject: [PATCH 27/31] update readme with better .env paths --- README.md | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 91244c6..46a4aa8 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ WOT Relay is a Nostr relay that saves all the notes that people you follow, and ## Prerequisites - **Go**: Ensure you have Go installed on your system. You can download it from [here](https://golang.org/dl/). +- **Build Essentials**: If you're using Linux, you may need to install build essentials. You can do this by running `sudo apt install build-essential`. ## Setup Instructions @@ -33,7 +34,9 @@ Open the `.env` file and set the necessary environment variables. Example variab RELAY_NAME="YourRelayName" RELAY_PUBKEY="YourPublicKey" RELAY_DESCRIPTION="Your relay description" -DB_PATH="/path/to/your/database" +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 ``` ### 4. Build the project @@ -97,34 +100,35 @@ To start the project using Docker Compose, follow these steps: 1. Ensure Docker and Docker Compose are installed on your system. 2. Navigate to the project directory. -3. Ensure the `.env` file is present in the project directory and has the necessary environment variables set. +3. Ensure the `.env` file is present in the project directory and has the necessary environment variables set. 4. You can also change the paths of the `db` folder and `templates` folder in the `docker-compose.yml` file. ```yaml - volumes: - - "./db:/app/db" # only change the left side before the colon - - "./templates/index.html:/app/templates/index.html" # only change the left side before the colon - - "./templates/static:/app/templates/static" # only change the left side before the colon + volumes: + - "./db:/app/db" # only change the left side before the colon + - "./templates/index.html:/app/templates/index.html" # only change the left side before the colon + - "./templates/static:/app/templates/static" # only change the left side before the colon ``` 5. Run the following command: - ```sh - # in foreground - docker compose up --build - # in background - docker compose up --build -d - ``` + ```sh + # in foreground + docker compose up --build + # in background + docker compose up --build -d + ``` + 6. For updating the relay, run the following command: - ```sh - git pull - docker compose build --no-cache - # in foreground - docker compose up - # in background - docker compose up -d - ``` + ```sh + git pull + docker compose build --no-cache + # in foreground + docker compose up + # in background + docker compose up -d + ``` This will build the Docker image and start the `wot-relay` service as defined in the `docker-compose.yml` file. The application will be accessible on port 3334. From a97e0ab2d8a19de8e5e20c92b3a63e6f3d30891b Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Sat, 7 Sep 2024 17:08:55 -0400 Subject: [PATCH 28/31] new context for each pull, no eose --- main.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index ba3ce7f..94a82fd 100644 --- a/main.go +++ b/main.go @@ -248,9 +248,10 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() - archivePool = nostr.NewSimplePool(ctx) for range ticker.C { - timeout, cancel := context.WithTimeout(ctx, 58*time.Second) + ctx := context.Background() + archivePool = nostr.NewSimplePool(ctx) + timeout, cancel := context.WithTimeout(ctx, 50*time.Second) filters := []nostr.Filter{{ Kinds: []int{ nostr.KindArticle, @@ -268,6 +269,7 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { }} nKeys := uint64(len(trustNetwork)) + fmt.Println("trust network size:", nKeys) bloomFilter := blobloom.NewOptimized(blobloom.Config{ Capacity: nKeys, FPRate: 1e-4, @@ -276,7 +278,7 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { bloomFilter.Add(xxhash.Sum64([]byte(trustedPubkey))) } - for ev := range archivePool.SubManyEose(timeout, relays, filters) { + for ev := range archivePool.SubMany(timeout, relays, filters) { if bloomFilter.Has(xxhash.Sum64([]byte(ev.Event.PubKey))) { if len(ev.Event.Tags) > 2000 { From d1198a31b59415da50f1346eeca0ff0854120cd6 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Sat, 7 Sep 2024 17:26:26 -0400 Subject: [PATCH 29/31] check for blank chunks --- main.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 94a82fd..43c2809 100644 --- a/main.go +++ b/main.go @@ -192,7 +192,6 @@ func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) []string { } } - fmt.Println("trust network size:", len(trustNetwork)) getTrustNetworkProfileMetadata(relay, ctx) } @@ -219,6 +218,10 @@ func getTrustNetworkProfileMetadata(relay *khatru.Relay, ctx context.Context) { } for _, chunk := range chunks { + if len(chunk) == 0 { + continue + } + timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() filters := []nostr.Filter{{ @@ -246,11 +249,10 @@ func appendPubkey(pubkey string) { func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { ticker := time.NewTicker(1 * time.Minute) + archivePool = nostr.NewSimplePool(ctx) defer ticker.Stop() for range ticker.C { - ctx := context.Background() - archivePool = nostr.NewSimplePool(ctx) timeout, cancel := context.WithTimeout(ctx, 50*time.Second) filters := []nostr.Filter{{ Kinds: []int{ @@ -282,10 +284,10 @@ func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { if bloomFilter.Has(xxhash.Sum64([]byte(ev.Event.PubKey))) { if len(ev.Event.Tags) > 2000 { - fmt.Println("skiping note with over 2000 tags") continue } relay.AddEvent(ctx, ev.Event) + } } cancel() From 55bae274907dd575f4ba0fb8687044545b711426 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Sat, 7 Sep 2024 17:28:42 -0400 Subject: [PATCH 30/31] explicit cancel --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 43c2809..589f848 100644 --- a/main.go +++ b/main.go @@ -223,7 +223,6 @@ func getTrustNetworkProfileMetadata(relay *khatru.Relay, ctx context.Context) { } timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second) - defer cancel() filters := []nostr.Filter{{ Authors: chunk, Kinds: []int{nostr.KindProfileMetadata}, @@ -232,6 +231,7 @@ func getTrustNetworkProfileMetadata(relay *khatru.Relay, ctx context.Context) { for ev := range fetchingPool.SubManyEose(timeoutCtx, relays, filters) { relay.AddEvent(ctx, ev.Event) } + cancel() } } From dd9763e967103870c1cef2b9878a4052de2a0b73 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Sat, 7 Sep 2024 20:14:43 -0400 Subject: [PATCH 31/31] performance optimization, sexyness upgrade --- main.go | 208 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 113 insertions(+), 95 deletions(-) diff --git a/main.go b/main.go index 589f848..83b376a 100644 --- a/main.go +++ b/main.go @@ -28,19 +28,33 @@ type Config struct { StaticPath string } -var archivePool *nostr.SimplePool -var fetchingPool *nostr.SimplePool - +var pool *nostr.SimplePool var relays []string var config Config var trustNetwork []string var mu sync.Mutex +var trustNetworkFilter *blobloom.Filter +var trustNetworkFilterMu sync.Mutex +var seedRelays []string func main() { - fmt.Println("starting") + green := "\033[32m" + reset := "\033[0m" + + art := ` + __ __ ___. _____ ___________ __ +/ \ / \ ____\_ |__ _____/ ____\ \__ ___/______ __ __ _______/ |_ +\ \/\/ // __ \| __ \ / _ \ __\ | | \_ __ \ | \/ ___/\ __\ + \ /\ ___/| \_\ \ ( <_> ) | | | | | \/ | /\___ \ | | + \__/\ / \___ >___ / \____/|__| |____| |__| |____//____ > |__| + \/ \/ \/ \/ + ` + + fmt.Println(green + art + reset) + log.Println("🚀 booting up web of trust relay") relay := khatru.NewRelay() ctx := context.Background() - + pool = nostr.NewSimplePool(ctx) config = LoadConfig() relay.Info.Name = config.RelayName @@ -49,7 +63,7 @@ func main() { appendPubkey(config.RelayPubkey) db := lmdb.LMDBBackend{ - Path: getEnv("DB_PATH"), + Path: config.DBPath, } if err := db.Init(); err != nil { panic(err) @@ -57,6 +71,7 @@ func main() { relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents) + relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent) relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) { for _, pk := range trustNetwork { if pk == event.PubKey { @@ -67,7 +82,7 @@ func main() { }) mu.Lock() - relays = []string{ + seedRelays = []string{ "wss://nos.lol", "wss://nostr.mom", "wss://purplepag.es", @@ -113,7 +128,7 @@ func main() { mux.Handle("/favicon.ico", http.StripPrefix("/", http.FileServer(http.Dir(config.StaticPath)))) - fmt.Println("running on :3334") + log.Println("🎉 relay running on port :3334") err := http.ListenAndServe(":3334", relay) if err != nil { log.Fatal(err) @@ -144,10 +159,23 @@ func getEnv(key string) string { return value } -func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) []string { - fetchingPool = nostr.NewSimplePool(ctx) +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 { - // Function to refresh the trust network runTrustNetworkRefresh := func() { timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() @@ -157,82 +185,76 @@ func refreshTrustNetwork(relay *khatru.Relay, ctx context.Context) []string { Kinds: []int{nostr.KindContactList}, }} - for ev := range fetchingPool.SubManyEose(timeoutCtx, relays, filters) { + 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]) } } - chunks := make([][]string, 0) - for i := 0; i < len(trustNetwork); i += 100 { - end := i + 100 - if end > len(trustNetwork) { - end = len(trustNetwork) - } - chunks = append(chunks, trustNetwork[i:end]) - } + follows := make([]string, len(trustNetwork)) + copy(follows, trustNetwork) - for _, chunk := range chunks { - threeTimeoutCtx, tenCancel := context.WithTimeout(ctx, 10*time.Second) - defer tenCancel() + 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) + } filters = []nostr.Filter{{ - Authors: chunk, - Kinds: []int{nostr.KindContactList}, + Authors: follows[i:end], + Kinds: []int{nostr.KindContactList, nostr.KindRelayListMetadata, nostr.KindProfileMetadata}, }} - for ev := range fetchingPool.SubManyEose(threeTimeoutCtx, relays, filters) { + for ev := range pool.SubManyEose(timeout, seedRelays, filters) { for _, contact := range ev.Event.Tags.GetAll([]string{"p"}) { if len(contact) > 1 { appendPubkey(contact[1]) - } else { - fmt.Println("Skipping malformed tag: ", contact) } } - } - } - getTrustNetworkProfileMetadata(relay, ctx) + for _, relay := range ev.Event.Tags.GetAll([]string{"r"}) { + appendRelay(relay[1]) + } + + if ev.Event.Kind == nostr.KindProfileMetadata { + relay.AddEvent(ctx, ev.Event) + } + } + + } + log.Println("🫂 network size:", len(trustNetwork)) + log.Println("🔗 relays discovered:", len(relays)) } runTrustNetworkRefresh() + updateTrustNetworkFilter() - ticker := time.NewTicker(10 * time.Minute) + ticker := time.NewTicker(24 * time.Hour) defer ticker.Stop() for range ticker.C { runTrustNetworkRefresh() + updateTrustNetworkFilter() } return trustNetwork } -func getTrustNetworkProfileMetadata(relay *khatru.Relay, ctx context.Context) { - chunks := make([][]string, 0) - for i := 0; i < len(trustNetwork); i += 100 { - end := i + 100 - if end > len(trustNetwork) { - end = len(trustNetwork) +func appendRelay(relay string) { + mu.Lock() + defer mu.Unlock() + + for _, r := range relays { + if r == relay { + return } - chunks = append(chunks, trustNetwork[i:end]) - } - - for _, chunk := range chunks { - if len(chunk) == 0 { - continue - } - - timeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second) - filters := []nostr.Filter{{ - Authors: chunk, - Kinds: []int{nostr.KindProfileMetadata}, - }} - - for ev := range fetchingPool.SubManyEose(timeoutCtx, relays, filters) { - relay.AddEvent(ctx, ev.Event) - } - cancel() } + relays = append(relays, relay) } func appendPubkey(pubkey string) { @@ -244,52 +266,48 @@ func appendPubkey(pubkey string) { return } } + + if len(pubkey) != 64 { + return + } + trustNetwork = append(trustNetwork, pubkey) } func archiveTrustedNotes(relay *khatru.Relay, ctx context.Context) { - ticker := time.NewTicker(1 * time.Minute) - archivePool = nostr.NewSimplePool(ctx) - defer ticker.Stop() + log.Println("⏳ waiting for trust network to be populated") + time.Sleep(1 * time.Minute) + timeout, cancel := context.WithTimeout(ctx, 24*time.Hour) + defer cancel() - for range ticker.C { - timeout, cancel := context.WithTimeout(ctx, 50*time.Second) - 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, - }, - }} - - nKeys := uint64(len(trustNetwork)) - fmt.Println("trust network size:", nKeys) - bloomFilter := blobloom.NewOptimized(blobloom.Config{ - Capacity: nKeys, - FPRate: 1e-4, - }) - for _, trustedPubkey := range trustNetwork { - bloomFilter.Add(xxhash.Sum64([]byte(trustedPubkey))) - } - - for ev := range archivePool.SubMany(timeout, relays, filters) { - - if bloomFilter.Has(xxhash.Sum64([]byte(ev.Event.PubKey))) { - if len(ev.Event.Tags) > 2000 { - continue - } - relay.AddEvent(ctx, ev.Event) + 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 } + relay.AddEvent(ctx, ev.Event) + i++ } - cancel() } + trustNetworkFilterMu.Unlock() + fmt.Println("📦 archived", i, "trusted notes") }