diff --git a/.env.example b/.env.example index f9a4232b..7d9b086d 100644 --- a/.env.example +++ b/.env.example @@ -104,4 +104,8 @@ WHITELISTED_NPUBS_FILE="whitelisted_npubs.json" BLACKLISTED_NPUBS_FILE="blacklisted_npubs.json" ## LOGGING -HAVEN_LOG_LEVEL="INFO" # DEBUG, INFO, WARNING or ERROR \ No newline at end of file +HAVEN_LOG_LEVEL="INFO" # DEBUG, INFO, WARNING or ERROR + +## PROXY / TOR (Optional) +# Set a SOCKS5 proxy URL to route all outgoing connections (e.g., Tor daemon) +PROXY_URL="" \ No newline at end of file diff --git a/blastr.go b/blastr.go index 09001245..27ac79b3 100644 --- a/blastr.go +++ b/blastr.go @@ -6,7 +6,7 @@ import ( "log/slog" "time" - "github.com/nbd-wtf/go-nostr" + "fiatjaf.com/nostr" ) func blast(ctx context.Context, ev *nostr.Event) { diff --git a/blossomMigration.go b/blossomMigration.go index c9faf436..d85c0564 100644 --- a/blossomMigration.go +++ b/blossomMigration.go @@ -4,7 +4,9 @@ import ( "context" "log/slog" - "github.com/fiatjaf/khatru/blossom" + "fiatjaf.com/nostr/khatru/blossom" + nipb0blossom "fiatjaf.com/nostr/nipb0/blossom" + "fiatjaf.com/nostr" ) func migrateBlossomMetadata(ctx context.Context, bl *blossom.BlossomServer) { @@ -12,14 +14,9 @@ func migrateBlossomMetadata(ctx context.Context, bl *blossom.BlossomServer) { outboxDBWrapper := blossom.EventStoreBlobIndexWrapper{Store: outboxDB, ServiceURL: getHTTPScheme(config.RelayURL) + config.RelayURL} // List all BlobDescriptor for the relay owner pubkey - ownerPubkey := nPubToPubkey("OWNER_NPUB", config.OwnerNpub) - blobsChan, err := outboxDBWrapper.List(ctx, ownerPubkey) - if err != nil { - slog.Error("🚫 Failed to list blobs", "error", err) - return - } - var blobs []blossom.BlobDescriptor - for blob := range blobsChan { + ownerPubkey := nostr.MustPubKeyFromHex(config.OwnerPubKey) + var blobs []nipb0blossom.BlobDescriptor + for blob := range outboxDBWrapper.List(ctx, ownerPubkey) { blobs = append(blobs, blob) } @@ -29,7 +26,7 @@ func migrateBlossomMetadata(ctx context.Context, bl *blossom.BlossomServer) { } // Create a map to track migrated blobs - migrated := make(map[string]blossom.BlobDescriptor, len(blobs)) + migrated := make(map[string]nipb0blossom.BlobDescriptor, len(blobs)) slog.Info("BlobDescriptors will be migrated from Outbox to Blossom's DB", "count", len(blobs)) diff --git a/config.go b/config.go index 10de3bbc..43530696 100644 --- a/config.go +++ b/config.go @@ -11,7 +11,8 @@ import ( "time" "github.com/joho/godotenv" - "github.com/nbd-wtf/go-nostr/nip19" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip19" ) type S3Config struct { @@ -66,6 +67,7 @@ type Config struct { LogLevel string `json:"log_level"` BlastrRelays []string `json:"blastr_relays"` BlastrTimeoutSeconds int `json:"blastr_timeout_seconds"` + ProxyURL string `json:"proxy_url"` S3Config *S3Config `json:"s3_config"` } @@ -118,6 +120,7 @@ func loadConfig() Config { LogLevel: getEnvString("HAVEN_LOG_LEVEL", "INFO"), BlastrRelays: getRelayListFromFile(getEnv("BLASTR_RELAYS_FILE")), BlastrTimeoutSeconds: getEnvInt("BLASTR_TIMEOUT_SECONDS", 5), + ProxyURL: getEnvString("PROXY_URL", ""), S3Config: getS3Config(), } @@ -166,7 +169,11 @@ func getRelayListFromFile(filePath string) []string { for i, relay := range relayList { relay = strings.TrimSpace(relay) if !strings.HasPrefix(relay, "wss://") && !strings.HasPrefix(relay, "ws://") { - relay = "wss://" + relay + if strings.Contains(relay, ".onion") { + relay = "ws://" + relay + } else { + relay = "wss://" + relay + } } relayList[i] = relay } @@ -261,8 +268,6 @@ func getEnvDuration(key string, defaultValue time.Duration) time.Duration { func nPubToPubkey(label, nPub string) string { prefix, v, err := nip19.Decode(nPub) if err != nil { - // Only echo the raw value when it looks like an npub; a malformed - // non-npub (e.g. a mistyped nsec) must not be leaked into logs. if strings.HasPrefix(nPub, "npub1") { log.Fatalf("invalid npub for %s: %q could not be decoded (%v)", label, nPub, err) } @@ -271,11 +276,15 @@ func nPubToPubkey(label, nPub string) string { if prefix != "npub" { log.Fatalf("invalid npub for %s: expected an npub, got a %q", label, prefix) } - pubkey, ok := v.(string) - if !ok { + switch value := v.(type) { + case string: + return value + case nostr.PubKey: + return value.Hex() + default: log.Fatalf("invalid npub for %s: %q did not decode to a public key", label, nPub) + return "" } - return pubkey } var art = ` diff --git a/go.mod b/go.mod index 474ccd43..9fc31bc6 100644 --- a/go.mod +++ b/go.mod @@ -1,43 +1,39 @@ module github.com/barrydeen/haven -go 1.24.1 +go 1.25 require ( - github.com/fiatjaf/eventstore v0.17.5 - github.com/fiatjaf/khatru v0.19.1 + fiatjaf.com/nostr v0.0.0-20260708142249-c591b5592910 github.com/joho/godotenv v1.5.1 github.com/minio/minio-go/v7 v7.0.98 - github.com/nbd-wtf/go-nostr v0.52.3 github.com/puzpuzpuz/xsync/v4 v4.4.0 github.com/spf13/afero v1.15.0 + golang.org/x/net v0.50.0 ) require ( - fiatjaf.com/lib v0.3.2 // indirect + fiatjaf.com/lib v0.3.7 // indirect + github.com/FastFilter/xorfilter v0.2.1 // indirect github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect github.com/PowerDNS/lmdb-go v1.9.3 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/bep/debounce v1.2.1 // indirect + github.com/btcsuite/btcd v0.24.2 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.6 // indirect github.com/btcsuite/btcd/btcutil v1.1.5 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect - github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.15.0 // indirect - github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect github.com/coder/websocket v1.8.14 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect - github.com/dgraph-io/badger/v4 v4.8.0 // indirect github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/elnosh/gonuts v0.4.2 // indirect github.com/fasthttp/websocket v1.5.12 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/flatbuffers v25.9.23+incompatible // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -56,23 +52,22 @@ require ( github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/templexxx/cpu v0.0.1 // indirect + github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tinylib/msgp v1.6.3 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.69.0 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.23.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect - golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c0e41cc5..6525474f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ -fiatjaf.com/lib v0.3.2 h1:RBS41z70d8Rp8e2nemQsbPY1NLLnEGShiY2c+Bom3+Q= -fiatjaf.com/lib v0.3.2/go.mod h1:UlHaZvPHj25PtKLh9GjZkUHRmQ2xZ8Jkoa4VRaLeeQ8= +fiatjaf.com/lib v0.3.7 h1:mXZOn7NrUcjSdy4oNvwQyAmes7Ueb+Zr5hjqMIe2dxI= +fiatjaf.com/lib v0.3.7/go.mod h1:UlHaZvPHj25PtKLh9GjZkUHRmQ2xZ8Jkoa4VRaLeeQ8= +fiatjaf.com/nostr v0.0.0-20260708142249-c591b5592910 h1:LplP012gQeGf2hwMcUllEfDrZvyQR6zvrXSNaNVcm20= +fiatjaf.com/nostr v0.0.0-20260708142249-c591b5592910/go.mod h1:b1EIUDnd133Ie8Pg8O/biaKdFyCMz28aD4n64g1GqvM= +github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc= +github.com/FastFilter/xorfilter v0.2.1/go.mod h1:aumvdkhscz6YBZF9ZA/6O4fIoNod4YR50kIVGGZ7l9I= github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg= github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3/go.mod h1:we0YA5CsBbH5+/NUzC/AlMmxaDtWlXeNsqrwXjTzmzA= github.com/PowerDNS/lmdb-go v1.9.3 h1:AUMY2pZT8WRpkEv39I9Id3MuoHd+NZbTVpNhruVkPTg= @@ -12,6 +16,8 @@ github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3IS github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.6 h1:IzlsEr9olcSRKB/n7c4351F3xHKxS2lma+1UFGCYd4E= @@ -33,16 +39,10 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= -github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= -github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= -github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= -github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +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/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= -github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -57,8 +57,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= -github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk= github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= @@ -66,21 +64,16 @@ github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUn github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= +github.com/elnosh/gonuts v0.4.2 h1:/WubPAWGxTE+okJ0WPvmtEzTzpi04RGxiTHAF1FYU+M= +github.com/elnosh/gonuts v0.4.2/go.mod h1:vgZomh4YQk7R3w4ltZc0sHwCmndfHkuX6V4sga/8oNs= github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE= github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg= -github.com/fiatjaf/eventstore v0.17.5 h1:/3CRthtZmkcTM01IxEiebydM5MKYZhZcQBKMSbLuWCs= -github.com/fiatjaf/eventstore v0.17.5/go.mod h1:8nWflHJ6E9DbBhRFqnpyI/zJGfYgxu2EMaTgayDGL4o= -github.com/fiatjaf/khatru v0.19.1 h1:n2m+cL9pdeb8WMhIDYbjct7jCirS9eHuMR0R7i2JGjw= -github.com/fiatjaf/khatru v0.19.1/go.mod h1:oYPexfQRBIDUPXWrPXjPqJksKCuK3Moc++rUI6Ubdb8= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -89,8 +82,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/flatbuffers v25.9.23+incompatible h1:rGZKv+wOb6QPzIdkM2KxhBZCDrA0DeN6DNmRDrqIsQU= -github.com/google/flatbuffers v25.9.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -118,8 +109,11 @@ github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzh github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM= github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM= @@ -137,8 +131,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nbd-wtf/go-nostr v0.52.3 h1:Xd87pXfJEJRXHpM+fLjQQln8dBNNaoPA10V7BbyP4KI= -github.com/nbd-wtf/go-nostr v0.52.3/go.mod h1:4avYoc9mDGZ9wHsvCOhHH9vPzKucCfuYBtJUSpHTfNk= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -170,19 +162,16 @@ github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761/go.mod h1:Vi9gvHvTw github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY= +github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= +github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3Wb1+pWBaWv/BlHK0ZYIu/KaL6eHg= +github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b/go.mod h1:7rwmCH0wC2fQvNEvPZ3sKXukhyCTyiaZ5VTZMQYpZKQ= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -193,26 +182,18 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= 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.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= -golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -228,6 +209,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -253,8 +236,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/import.go b/import.go index 56e505cf..1765bde2 100644 --- a/import.go +++ b/import.go @@ -11,8 +11,7 @@ import ( "slices" "time" - "github.com/fiatjaf/eventstore" - "github.com/nbd-wtf/go-nostr" + "fiatjaf.com/nostr" "github.com/barrydeen/haven/pkg/wot" ) @@ -72,7 +71,6 @@ func runImport(ctx context.Context) { func importOwnerNotes(ctx context.Context) { ownerImportedNotes := 0 nFailedImportNotes := 0 - wdb := eventstore.RelayWrapper{Store: outboxDB} startTime, err := time.Parse(layout, config.ImportStartDate) if err != nil { @@ -84,11 +82,15 @@ func importOwnerNotes(ctx context.Context) { for { startTimestamp := nostr.Timestamp(startTime.Unix()) endTimestamp := nostr.Timestamp(endTime.Unix()) + authors := make([]nostr.PubKey, 0, len(config.WhitelistedPubKeys)) + for pubkeyHex := range config.WhitelistedPubKeys { + authors = append(authors, nostr.MustPubKeyFromHex(pubkeyHex)) + } filter := nostr.Filter{ - Authors: slices.Collect(maps.Keys(config.WhitelistedPubKeys)), - Since: &startTimestamp, - Until: &endTimestamp, + Authors: authors, + Since: startTimestamp, + Until: endTimestamp, } done := make(chan int, 1) @@ -99,16 +101,16 @@ func importOwnerNotes(ctx context.Context) { defer cancel() batchImportedNotes := 0 - events := pool.FetchMany(ctx, config.ImportSeedRelays, filter) + events := pool.FetchMany(ctx, config.ImportSeedRelays, filter, nostr.SubscriptionOptions{}) for ev := range events { if ctx.Err() != nil { break // Stop the loop on timeout } - if _, ok := config.BlacklistedPubKeys[ev.PubKey]; ok { + if _, ok := config.BlacklistedPubKeys[ev.PubKey.Hex()]; ok { slog.Debug("🚫 skipping event from blacklisted pubkey", "pubkey", ev.PubKey, "id", ev.ID) continue } - if err := wdb.Publish(ctx, *ev.Event); err != nil { + if err := outboxDB.SaveEvent(ev.Event); err != nil { log.Println("🚫 error importing note", ev.ID, ":", err) nFailedImportNotes++ } @@ -152,8 +154,8 @@ func importTaggedNotes(ctx context.Context) { ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - wdbInbox := eventstore.RelayWrapper{Store: inboxDB} - wdbChat := eventstore.RelayWrapper{Store: chatDB} + inboxStore := inboxDB + chatStore := chatDB filter := nostr.Filter{ Tags: nostr.TagMap{ "p": slices.Collect(maps.Keys(config.WhitelistedPubKeys)), @@ -163,18 +165,18 @@ func importTaggedNotes(ctx context.Context) { log.Println("πŸ“¦ importing inbox notes, please wait up to", timeout) go func() { - events := pool.FetchMany(ctx, config.ImportSeedRelays, filter) + events := pool.FetchMany(ctx, config.ImportSeedRelays, filter, nostr.SubscriptionOptions{}) for ev := range events { if ctx.Err() != nil { break // Stop the loop on timeout } - if _, ok := config.BlacklistedPubKeys[ev.PubKey]; ok { + if _, ok := config.BlacklistedPubKeys[ev.PubKey.Hex()]; ok { slog.Debug("🚫 skipping tagged event from blacklisted pubkey", "pubkey", ev.PubKey, "id", ev.ID) continue } - if !wot.GetInstance().Has(ctx, ev.PubKey) && ev.Kind != nostr.KindGiftWrap { + if !wot.GetInstance().Has(ctx, ev.PubKey.Hex()) && ev.Kind != nostr.KindGiftWrap { continue } for tag := range ev.Tags.FindAll("p") { @@ -182,11 +184,11 @@ func importTaggedNotes(ctx context.Context) { continue } if _, ok := config.WhitelistedPubKeys[tag[1]]; ok { - dbToWrite := wdbInbox + dbToWrite := inboxStore if ev.Kind == nostr.KindGiftWrap { - dbToWrite = wdbChat + dbToWrite = chatStore } - if err := dbToWrite.Publish(ctx, *ev.Event); err != nil { + if err := dbToWrite.SaveEvent(ev.Event); err != nil { log.Println("🚫 error importing tagged note", ev.ID, ":", err) } taggedImportedNotes++ @@ -207,24 +209,22 @@ func importTaggedNotes(ctx context.Context) { } func subscribeInboxAndChat(ctx context.Context) { - wdbInbox := eventstore.RelayWrapper{Store: inboxDB} - wdbChat := eventstore.RelayWrapper{Store: chatDB} startTime := nostr.Timestamp(time.Now().Add(-time.Minute * 5).Unix()) filter := nostr.Filter{ Tags: nostr.TagMap{ "p": slices.Collect(maps.Keys(config.WhitelistedPubKeys)), }, - Since: &startTime, + Since: startTime, } log.Println("πŸ“’ subscribing to inbox") - for ev := range pool.SubscribeMany(ctx, config.ImportSeedRelays, filter) { - if _, ok := config.BlacklistedPubKeys[ev.PubKey]; ok { + for ev := range pool.SubscribeMany(ctx, config.ImportSeedRelays, filter, nostr.SubscriptionOptions{}) { + if _, ok := config.BlacklistedPubKeys[ev.PubKey.Hex()]; ok { slog.Debug("🚫discarding imported note from blacklisted pubkey", "pubkey", ev.PubKey, "id", ev.ID) continue } - if !wot.GetInstance().Has(ctx, ev.PubKey) && ev.Kind != nostr.KindGiftWrap { + if !wot.GetInstance().Has(ctx, ev.PubKey.Hex()) && ev.Kind != nostr.KindGiftWrap { continue } for tag := range ev.Tags.FindAll("p") { @@ -232,9 +232,9 @@ func subscribeInboxAndChat(ctx context.Context) { continue } if _, ok := config.WhitelistedPubKeys[tag[1]]; ok { - dbToPublish := wdbInbox + dbToPublish := inboxDB if ev.Kind == nostr.KindGiftWrap { - dbToPublish = wdbChat + dbToPublish = chatDB } slog.Debug("ℹ️ importing event", "kind", ev.Kind, "id", ev.ID, "relay", ev.Relay.URL) @@ -244,7 +244,7 @@ func subscribeInboxAndChat(ctx context.Context) { break // Avoid re-importing duplicates } - if err := dbToPublish.Publish(ctx, *ev.Event); err != nil { + if err := dbToPublish.SaveEvent(ev.Event); err != nil { log.Println("🚫 error importing tagged note", ev.ID, ":", "from relay", ev.Relay.URL, ":", err) break } @@ -272,18 +272,16 @@ func subscribeInboxAndChat(ctx context.Context) { } } -func isDuplicate(ctx context.Context, db eventstore.RelayWrapper, event *nostr.Event) bool { +func isDuplicate(ctx context.Context, db DBBackend, event nostr.Event) bool { filter := nostr.Filter{ - IDs: []string{event.ID}, - Since: &event.CreatedAt, + IDs: []nostr.ID{event.ID}, + Since: event.CreatedAt, Limit: 1, } - events, err := db.QuerySync(ctx, filter) - if err != nil { - log.Println("🚫 error querying for event", event.ID, ":", err) - return false + for range db.QueryEvents(filter, 1) { + return true } - return len(events) > 0 + return false } diff --git a/init.go b/init.go index 17fbccc6..94e8ad71 100644 --- a/init.go +++ b/init.go @@ -6,16 +6,17 @@ import ( "io" "log/slog" "net/http" + "net/url" "strings" "text/template" "time" - "github.com/fiatjaf/eventstore/badger" - "github.com/fiatjaf/eventstore/lmdb" - "github.com/fiatjaf/khatru" - "github.com/fiatjaf/khatru/blossom" - "github.com/fiatjaf/khatru/policies" - "github.com/nbd-wtf/go-nostr" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/eventstore" + "fiatjaf.com/nostr/eventstore/lmdb" + "fiatjaf.com/nostr/khatru" + "fiatjaf.com/nostr/khatru/blossom" + "fiatjaf.com/nostr/khatru/policies" ) // getHTTPScheme returns the appropriate HTTP scheme based on the URL. @@ -66,25 +67,12 @@ var dbs = map[string]DBBackend{ "private": privateDB, } -type DBBackend interface { - Init() error - Close() - CountEvents(ctx context.Context, filter nostr.Filter) (int64, error) - DeleteEvent(ctx context.Context, evt *nostr.Event) error - QueryEvents(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) - SaveEvent(ctx context.Context, evt *nostr.Event) error - ReplaceEvent(ctx context.Context, evt *nostr.Event) error - Serial() []byte -} +type DBBackend = eventstore.Store func newDBBackend(path string) DBBackend { switch config.DBEngine { case "lmdb": return newLMDBBackend(path) - case "badger": - return &badger.BadgerBackend{ - Path: path, - } default: return newLMDBBackend(path) } @@ -125,46 +113,54 @@ func initRelays(ctx context.Context) { initRelayLimits() privateRelay.Info.Name = config.PrivateRelayName - privateRelay.Info.PubKey = nPubToPubkey("PRIVATE_RELAY_NPUB", config.PrivateRelayNpub) + privatePrivatePubKey := nostr.MustPubKeyFromHex(nPubToPubkey("PRIVATE_RELAY_NPUB", config.PrivateRelayNpub)) + privateRelay.Info.PubKey = &privatePrivatePubKey privateRelay.Info.Description = config.PrivateRelayDescription privateRelay.Info.Icon = config.PrivateRelayIcon privateRelay.Info.Version = config.RelayVersion privateRelay.Info.Software = config.RelaySoftware privateRelay.ServiceURL = getHTTPScheme(config.RelayURL) + config.RelayURL + "/private" - if !privateRelayLimits.AllowEmptyFilters { - privateRelay.RejectFilter = append(privateRelay.RejectFilter, policies.NoEmptyFilters) - } - if !privateRelayLimits.AllowComplexFilters { - privateRelay.RejectFilter = append(privateRelay.RejectFilter, policies.NoComplexFilters) + privateRelay.OnRequest = func(ctx context.Context, filter nostr.Filter) (bool, string) { + if !privateRelayLimits.AllowEmptyFilters { + if reject, msg := policies.NoEmptyFilters(ctx, filter); reject { + return reject, msg + } + } + if !privateRelayLimits.AllowComplexFilters { + if reject, msg := policies.NoComplexFilters(ctx, filter); reject { + return reject, msg + } + } + if reject, msg := policies.MustAuth(ctx, filter); reject { + return reject, msg + } + return MustBeWhitelistedToQuery(ctx, filter) } - privateRelay.RejectFilter = append(privateRelay.RejectFilter, policies.MustAuth, MustBeWhitelistedToQuery) - privateRelay.RejectEvent = append(privateRelay.RejectEvent, - policies.RejectEventsWithBase64Media, - policies.EventIPRateLimiter( + privateRelay.OnEvent = func(ctx context.Context, event nostr.Event) (bool, string) { + if reject, msg := policies.RejectEventsWithBase64Media(ctx, event); reject { + return reject, msg + } + if reject, msg := policies.EventIPRateLimiter( privateRelayLimits.EventIPLimiterTokensPerInterval, time.Minute*time.Duration(privateRelayLimits.EventIPLimiterInterval), privateRelayLimits.EventIPLimiterMaxTokens, - ), - MustBeWhitelistedToPost, - ) + )(ctx, event); reject { + return reject, msg + } + return MustBeWhitelistedToPost(ctx, &event) + } - privateRelay.RejectConnection = append(privateRelay.RejectConnection, - policies.ConnectionRateLimiter( - privateRelayLimits.ConnectionRateLimiterTokensPerInterval, - time.Minute*time.Duration(privateRelayLimits.ConnectionRateLimiterInterval), - privateRelayLimits.ConnectionRateLimiterMaxTokens, - ), + privateRelay.RejectConnection = policies.ConnectionRateLimiter( + privateRelayLimits.ConnectionRateLimiterTokensPerInterval, + time.Minute*time.Duration(privateRelayLimits.ConnectionRateLimiterInterval), + privateRelayLimits.ConnectionRateLimiterMaxTokens, ) - privateRelay.OnConnect = append(privateRelay.OnConnect, khatru.RequestAuth) + privateRelay.OnConnect = khatru.RequestAuth - privateRelay.StoreEvent = append(privateRelay.StoreEvent, privateDB.SaveEvent) - privateRelay.QueryEvents = append(privateRelay.QueryEvents, privateDB.QueryEvents) - privateRelay.DeleteEvent = append(privateRelay.DeleteEvent, privateDB.DeleteEvent) - privateRelay.CountEvents = append(privateRelay.CountEvents, privateDB.CountEvents) - privateRelay.ReplaceEvent = append(privateRelay.ReplaceEvent, privateDB.ReplaceEvent) + privateRelay.UseEventstore(privateDB, 1000) mux := privateRelay.Router() @@ -188,48 +184,60 @@ func initRelays(ctx context.Context) { }) chatRelay.Info.Name = config.ChatRelayName - chatRelay.Info.PubKey = nPubToPubkey("CHAT_RELAY_NPUB", config.ChatRelayNpub) + chatPrivatePubKey := nostr.MustPubKeyFromHex(nPubToPubkey("CHAT_RELAY_NPUB", config.ChatRelayNpub)) + chatRelay.Info.PubKey = &chatPrivatePubKey chatRelay.Info.Description = config.ChatRelayDescription chatRelay.Info.Icon = config.ChatRelayIcon chatRelay.Info.Version = config.RelayVersion chatRelay.Info.Software = config.RelaySoftware chatRelay.ServiceURL = getHTTPScheme(config.RelayURL) + config.RelayURL + "/chat" - if !chatRelayLimits.AllowEmptyFilters { - chatRelay.RejectFilter = append(chatRelay.RejectFilter, policies.NoEmptyFilters) - } - if !chatRelayLimits.AllowComplexFilters { - chatRelay.RejectFilter = append(chatRelay.RejectFilter, policies.NoComplexFilters) + chatRelay.OnRequest = func(ctx context.Context, filter nostr.Filter) (bool, string) { + if !chatRelayLimits.AllowEmptyFilters { + if reject, msg := policies.NoEmptyFilters(ctx, filter); reject { + return reject, msg + } + } + if !chatRelayLimits.AllowComplexFilters { + if reject, msg := policies.NoComplexFilters(ctx, filter); reject { + return reject, msg + } + } + if reject, msg := policies.MustAuth(ctx, filter); reject { + return reject, msg + } + return MustBeInWotToQuery(ctx, filter) } - chatRelay.RejectFilter = append(chatRelay.RejectFilter, policies.MustAuth, MustBeInWotToQuery) - chatRelay.RejectEvent = append(chatRelay.RejectEvent, - policies.RejectEventsWithBase64Media, - policies.EventIPRateLimiter( + chatRelay.OnEvent = func(ctx context.Context, event nostr.Event) (bool, string) { + if reject, msg := policies.RejectEventsWithBase64Media(ctx, event); reject { + return reject, msg + } + if reject, msg := policies.EventIPRateLimiter( chatRelayLimits.EventIPLimiterTokensPerInterval, time.Minute*time.Duration(chatRelayLimits.EventIPLimiterInterval), chatRelayLimits.EventIPLimiterMaxTokens, - ), - MustNotBeBlacklistedToPost, - MustBeInWotToPost, - EventMustBeChatRelated, - ) + )(ctx, event); reject { + return reject, msg + } + if reject, msg := MustNotBeBlacklistedToPost(ctx, &event); reject { + return reject, msg + } + if reject, msg := MustBeInWotToPost(ctx, &event); reject { + return reject, msg + } + return EventMustBeChatRelated(ctx, &event) + } - chatRelay.RejectConnection = append(chatRelay.RejectConnection, - policies.ConnectionRateLimiter( - chatRelayLimits.ConnectionRateLimiterTokensPerInterval, - time.Minute*time.Duration(chatRelayLimits.ConnectionRateLimiterInterval), - chatRelayLimits.ConnectionRateLimiterMaxTokens, - ), + chatRelay.RejectConnection = policies.ConnectionRateLimiter( + chatRelayLimits.ConnectionRateLimiterTokensPerInterval, + time.Minute*time.Duration(chatRelayLimits.ConnectionRateLimiterInterval), + chatRelayLimits.ConnectionRateLimiterMaxTokens, ) - chatRelay.OnConnect = append(chatRelay.OnConnect, khatru.RequestAuth) + chatRelay.OnConnect = khatru.RequestAuth - chatRelay.StoreEvent = append(chatRelay.StoreEvent, chatDB.SaveEvent) - chatRelay.QueryEvents = append(chatRelay.QueryEvents, chatDB.QueryEvents) - chatRelay.DeleteEvent = append(chatRelay.DeleteEvent, chatDB.DeleteEvent) - chatRelay.CountEvents = append(chatRelay.CountEvents, chatDB.CountEvents) - chatRelay.ReplaceEvent = append(chatRelay.ReplaceEvent, chatDB.ReplaceEvent) + chatRelay.UseEventstore(chatDB, 1000) mux = chatRelay.Router() @@ -253,46 +261,52 @@ func initRelays(ctx context.Context) { }) outboxRelay.Info.Name = config.OutboxRelayName - outboxRelay.Info.PubKey = nPubToPubkey("OUTBOX_RELAY_NPUB", config.OutboxRelayNpub) + outboxOutboxPubKey := nostr.MustPubKeyFromHex(nPubToPubkey("OUTBOX_RELAY_NPUB", config.OutboxRelayNpub)) + outboxRelay.Info.PubKey = &outboxOutboxPubKey outboxRelay.Info.Description = config.OutboxRelayDescription outboxRelay.Info.Icon = config.OutboxRelayIcon outboxRelay.Info.Version = config.RelayVersion outboxRelay.Info.Software = config.RelaySoftware outboxRelay.ServiceURL = getHTTPScheme(config.RelayURL) + config.RelayURL - if !outboxRelayLimits.AllowEmptyFilters { - outboxRelay.RejectFilter = append(outboxRelay.RejectFilter, policies.NoEmptyFilters) - } - if !outboxRelayLimits.AllowComplexFilters { - outboxRelay.RejectFilter = append(outboxRelay.RejectFilter, policies.NoComplexFilters) + outboxRelay.OnRequest = func(ctx context.Context, filter nostr.Filter) (bool, string) { + if !outboxRelayLimits.AllowEmptyFilters { + if reject, msg := policies.NoEmptyFilters(ctx, filter); reject { + return reject, msg + } + } + if !outboxRelayLimits.AllowComplexFilters { + if reject, msg := policies.NoComplexFilters(ctx, filter); reject { + return reject, msg + } + } + return false, "" } - outboxRelay.RejectEvent = append(outboxRelay.RejectEvent, - policies.RejectEventsWithBase64Media, - policies.EventIPRateLimiter( + outboxRelay.OnEvent = func(ctx context.Context, event nostr.Event) (bool, string) { + if reject, msg := policies.RejectEventsWithBase64Media(ctx, event); reject { + return reject, msg + } + if reject, msg := policies.EventIPRateLimiter( outboxRelayLimits.EventIPLimiterTokensPerInterval, time.Minute*time.Duration(outboxRelayLimits.EventIPLimiterInterval), outboxRelayLimits.EventIPLimiterMaxTokens, - ), - MustBeWhitelistedToPost, - ) + )(ctx, event); reject { + return reject, msg + } + return MustBeWhitelistedToPost(ctx, &event) + } - outboxRelay.RejectConnection = append(outboxRelay.RejectConnection, - policies.ConnectionRateLimiter( - outboxRelayLimits.ConnectionRateLimiterTokensPerInterval, - time.Minute*time.Duration(outboxRelayLimits.ConnectionRateLimiterInterval), - outboxRelayLimits.ConnectionRateLimiterMaxTokens, - ), + outboxRelay.RejectConnection = policies.ConnectionRateLimiter( + outboxRelayLimits.ConnectionRateLimiterTokensPerInterval, + time.Minute*time.Duration(outboxRelayLimits.ConnectionRateLimiterInterval), + outboxRelayLimits.ConnectionRateLimiterMaxTokens, ) - outboxRelay.StoreEvent = append(outboxRelay.StoreEvent, outboxDB.SaveEvent, func(ctx context.Context, event *nostr.Event) error { - go blast(ctx, event) - return nil - }) - outboxRelay.QueryEvents = append(outboxRelay.QueryEvents, outboxDB.QueryEvents) - outboxRelay.DeleteEvent = append(outboxRelay.DeleteEvent, outboxDB.DeleteEvent) - outboxRelay.CountEvents = append(outboxRelay.CountEvents, outboxDB.CountEvents) - outboxRelay.ReplaceEvent = append(outboxRelay.ReplaceEvent, outboxDB.ReplaceEvent) + outboxRelay.UseEventstore(outboxDB, 1000) + outboxRelay.OnEventSaved = func(ctx context.Context, event nostr.Event) { + go blast(ctx, &event) + } mux = outboxRelay.Router() @@ -318,7 +332,7 @@ func initRelays(ctx context.Context) { bl := blossom.New(outboxRelay, getHTTPScheme(config.RelayURL)+config.RelayURL) bl.Store = blossom.EventStoreBlobIndexWrapper{Store: blossomDB, ServiceURL: bl.ServiceURL} - bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, ext string, body []byte) error { + bl.StoreBlob = func(ctx context.Context, sha256 string, ext string, body []byte) error { slog.Debug("storing blob", "sha256", sha256, "ext", ext) file, err := fs.Create(config.BlossomPath + sha256) if err != nil { @@ -328,65 +342,81 @@ func initRelays(ctx context.Context) { return err } return nil - }) - bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, error) { + } + bl.LoadBlob = func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, *url.URL, error) { slog.Debug("loading blob", "sha256", sha256, "ext", ext) - return fs.Open(config.BlossomPath + sha256) - }) - bl.DeleteBlob = append(bl.DeleteBlob, func(ctx context.Context, sha256 string, ext string) error { + file, err := fs.Open(config.BlossomPath + sha256) + if err != nil { + return nil, nil, err + } + return file, nil, nil + } + bl.DeleteBlob = func(ctx context.Context, sha256 string, ext string) error { slog.Debug("deleting blob", "sha256", sha256, "ext", ext) return fs.Remove(config.BlossomPath + sha256) - }) - bl.RejectUpload = append(bl.RejectUpload, func(ctx context.Context, event *nostr.Event, size int, ext string) (bool, string, int) { - if _, ok := config.WhitelistedPubKeys[event.PubKey]; ok { + } + bl.RejectUpload = func(ctx context.Context, event *nostr.Event, size int, ext string) (bool, string, int) { + if _, ok := config.WhitelistedPubKeys[event.PubKey.Hex()]; ok { return false, ext, size } return true, "only media signed by whitelisted pubkeys are allowed", 403 - }) + } migrateBlossomMetadata(ctx, bl) inboxRelay.Info.Name = config.InboxRelayName - inboxRelay.Info.PubKey = nPubToPubkey("INBOX_RELAY_NPUB", config.InboxRelayNpub) + inboxInboxPubKey := nostr.MustPubKeyFromHex(nPubToPubkey("INBOX_RELAY_NPUB", config.InboxRelayNpub)) + inboxRelay.Info.PubKey = &inboxInboxPubKey inboxRelay.Info.Description = config.InboxRelayDescription inboxRelay.Info.Icon = config.InboxRelayIcon inboxRelay.Info.Version = config.RelayVersion inboxRelay.Info.Software = config.RelaySoftware inboxRelay.ServiceURL = getHTTPScheme(config.RelayURL) + config.RelayURL + "/inbox" - if !inboxRelayLimits.AllowEmptyFilters { - inboxRelay.RejectFilter = append(inboxRelay.RejectFilter, policies.NoEmptyFilters) - } - if !inboxRelayLimits.AllowComplexFilters { - inboxRelay.RejectFilter = append(inboxRelay.RejectFilter, policies.NoComplexFilters) + inboxRelay.OnRequest = func(ctx context.Context, filter nostr.Filter) (bool, string) { + if !inboxRelayLimits.AllowEmptyFilters { + if reject, msg := policies.NoEmptyFilters(ctx, filter); reject { + return reject, msg + } + } + if !inboxRelayLimits.AllowComplexFilters { + if reject, msg := policies.NoComplexFilters(ctx, filter); reject { + return reject, msg + } + } + return false, "" } - inboxRelay.RejectEvent = append(inboxRelay.RejectEvent, - policies.RejectEventsWithBase64Media, - policies.EventIPRateLimiter( + inboxRelay.OnEvent = func(ctx context.Context, event nostr.Event) (bool, string) { + if reject, msg := policies.RejectEventsWithBase64Media(ctx, event); reject { + return reject, msg + } + if reject, msg := policies.EventIPRateLimiter( inboxRelayLimits.EventIPLimiterTokensPerInterval, time.Minute*time.Duration(inboxRelayLimits.EventIPLimiterInterval), inboxRelayLimits.EventIPLimiterMaxTokens, - ), - OnlyGiftWrappedDMs, - MustNotBeBlacklistedToPost, - MustBeInWotToPost, - MustTagWhitelistedPubKey, - ) + )(ctx, event); reject { + return reject, msg + } + if reject, msg := OnlyGiftWrappedDMs(ctx, &event); reject { + return reject, msg + } + if reject, msg := MustNotBeBlacklistedToPost(ctx, &event); reject { + return reject, msg + } + if reject, msg := MustBeInWotToPost(ctx, &event); reject { + return reject, msg + } + return MustTagWhitelistedPubKey(ctx, &event) + } - inboxRelay.RejectConnection = append(inboxRelay.RejectConnection, - policies.ConnectionRateLimiter( - inboxRelayLimits.ConnectionRateLimiterTokensPerInterval, - time.Minute*time.Duration(inboxRelayLimits.ConnectionRateLimiterInterval), - inboxRelayLimits.ConnectionRateLimiterMaxTokens, - ), + inboxRelay.RejectConnection = policies.ConnectionRateLimiter( + inboxRelayLimits.ConnectionRateLimiterTokensPerInterval, + time.Minute*time.Duration(inboxRelayLimits.ConnectionRateLimiterInterval), + inboxRelayLimits.ConnectionRateLimiterMaxTokens, ) - inboxRelay.StoreEvent = append(inboxRelay.StoreEvent, inboxDB.SaveEvent) - inboxRelay.QueryEvents = append(inboxRelay.QueryEvents, inboxDB.QueryEvents) - inboxRelay.DeleteEvent = append(inboxRelay.DeleteEvent, inboxDB.DeleteEvent) - inboxRelay.CountEvents = append(inboxRelay.CountEvents, inboxDB.CountEvents) - inboxRelay.ReplaceEvent = append(inboxRelay.ReplaceEvent, inboxDB.ReplaceEvent) + inboxRelay.UseEventstore(inboxDB, 1000) mux = inboxRelay.Router() diff --git a/jsonl.go b/jsonl.go index 6aefce99..622cf624 100644 --- a/jsonl.go +++ b/jsonl.go @@ -3,7 +3,6 @@ package main import ( "archive/zip" "bufio" - "cmp" "context" "encoding/json" "errors" @@ -15,8 +14,8 @@ import ( "strings" "time" - "github.com/fiatjaf/eventstore" - "github.com/nbd-wtf/go-nostr" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/eventstore" ) func (z *zipWriter) close() { @@ -186,7 +185,7 @@ func importDB(ctx context.Context, db DBBackend, r io.Reader) error { return err } - if err := db.SaveEvent(ctx, &event); err != nil { + if err := db.SaveEvent(event); err != nil { if errors.Is(err, eventstore.ErrDupEvent) { slog.Debug("⏭️ skipping duplicate event", "id", event.ID) continue @@ -209,7 +208,7 @@ func exportDB(ctx context.Context, db DBBackend, w io.Writer) error { var lastTimestamp nostr.Timestamp count := 0 - var eventBuffer []*nostr.Event + var eventBuffer []nostr.Event flushBuffer := func() error { for _, e := range eventBuffer { @@ -227,13 +226,10 @@ func exportDB(ctx context.Context, db DBBackend, w io.Writer) error { Limit: limit, } if lastTimestamp != 0 { - filter.Until = &lastTimestamp + filter.Until = lastTimestamp } - events, err := db.QueryEvents(ctx, filter) - if err != nil { - return err - } + events := db.QueryEvents(filter, limit) initialCount := count initialBufferSize := len(eventBuffer) @@ -255,8 +251,8 @@ func exportDB(ctx context.Context, db DBBackend, w io.Writer) error { // 2. Roundtrip consistency is maintained when importing back into a DB, even // if the original source had duplicates (e.g. due to bugs in older versions // of eventstore and khatru). - pos, found := slices.BinarySearchFunc(eventBuffer, event, func(a, b *nostr.Event) int { - return cmp.Compare(a.ID, b.ID) + pos, found := slices.BinarySearchFunc(eventBuffer, event, func(a, b nostr.Event) int { + return nostr.CompareEvent(a, b) }) if !found { eventBuffer = slices.Insert(eventBuffer, pos, event) diff --git a/main.go b/main.go index aa23e6aa..e3447338 100644 --- a/main.go +++ b/main.go @@ -2,27 +2,116 @@ package main import ( "context" + "encoding/json" "flag" "fmt" "io" "log" "log/slog" + "net" "net/http" "os" - "github.com/fiatjaf/khatru" - "github.com/nbd-wtf/go-nostr" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/khatru" + "golang.org/x/net/proxy" "github.com/spf13/afero" "github.com/barrydeen/haven/pkg/wot" ) var ( - pool *nostr.SimplePool + pool *nostr.Pool config = loadConfig() fs afero.Fs ) +// testTorConnectivity verifies that the Tor proxy is working by checking against Tor Project's official service. +// It relies on the process-wide http.DefaultTransport already being configured. +func testTorConnectivity() { + client := &http.Client{ + Transport: http.DefaultTransport, + Timeout: http.DefaultClient.Timeout, + } + + // Use the official Tor Project API to verify we're connected through Tor + resp, err := client.Get("https://check.torproject.org/api/ip") + if err != nil { + log.Println("⚠️ Debug: Could not verify Tor connectivity:", err) + return + } + defer func() { _ = resp.Body.Close() }() + + // Parse the JSON response + var result map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + log.Println("⚠️ Debug: Could not parse Tor check response:", err) + return + } + + // Check if the response indicates we're using Tor + isTor, ok := result["IsTor"].(bool) + if !ok { + log.Println("⚠️ Debug: Could not determine Tor status from response") + return + } + + if isTor { + log.Println("βœ… Successfully verified - traffic is routing through Tor network!") + if ip, ok := result["IP"].(string); ok { + log.Printf("πŸ§… Tor exit node IP: %s\n", ip) + } + } else { + log.Println("⚠️ Debug: ❌ WARNING - traffic is NOT routing through Tor") + if ip, ok := result["IP"].(string); ok { + log.Printf("⚠️ Debug: Current IP: %s\n", ip) + } + } +} + +// createPoolWithProxy creates a nostr relay pool with optional SOCKS5 proxy support. +// If PROXY_URL environment variable is set, all outgoing connections will route through the proxy. +// This is useful for privacy-preserving setups using Tor. +func createPoolWithProxy(ctx context.Context) *nostr.Pool { + if config.ProxyURL != "" { + log.Println("πŸ”’ Proxy configured - routing ALL connections through SOCKS5:", config.ProxyURL) + + // Create a dialer that uses SOCKS5 for connection routing + dialer, err := proxy.SOCKS5("tcp", config.ProxyURL, nil, &net.Dialer{}) + if err != nil { + log.Fatalf("failed to create SOCKS5 dialer: %s", err) + } + + // Create custom transport with SOCKS5 dialer + transport := &http.Transport{ + Dial: dialer.Dial, + } + + // Set as default transport for all http clients in the program + // This ensures ALL outgoing HTTP/HTTPS connections use the proxy + http.DefaultTransport = transport + + log.Println("βœ… SOCKS5 proxy initialized - all outgoing connections will route through proxy") + + // Debug: test Tor connectivity using the process-wide default transport. + testTorConnectivity() + } else { + // Default pool without proxy + log.Println("No proxy configured - connections will use direct IP") + } + + newPool := nostr.NewPool() + newPool.Context = ctx + newPool.RelayOptions = nostr.RelayOptions{ + RequestHeader: http.Header{ + "User-Agent": []string{config.UserAgent}, + }, + } + newPool.StartPenaltyBox() + + return newPool +} + func main() { nostr.InfoLogger = log.New(io.Discard, "", 0) slog.SetLogLoggerLevel(getLogLevelFromConfig()) @@ -38,13 +127,7 @@ func main() { log.Fatal("🚫 error creating blossom path:", err) } - pool = nostr.NewSimplePool(mainCtx, - nostr.WithPenaltyBox(), - nostr.WithRelayOptions( - nostr.WithRequestHeader{ - "User-Agent": []string{config.UserAgent}, - }), - ) + pool = createPoolWithProxy(mainCtx) if len(os.Args) > 1 { switch os.Args[1] { diff --git a/pkg/wot/simple_in_memory.go b/pkg/wot/simple_in_memory.go index c84b533e..e53a65f7 100644 --- a/pkg/wot/simple_in_memory.go +++ b/pkg/wot/simple_in_memory.go @@ -10,7 +10,7 @@ import ( "sync/atomic" "time" - "github.com/nbd-wtf/go-nostr" + "fiatjaf.com/nostr" "github.com/puzpuzpuz/xsync/v4" ) @@ -20,7 +20,7 @@ type SimpleInMemory struct { pubkeys atomic.Pointer[map[string]bool] // Dependencies for Refresh - Pool *nostr.SimplePool + Pool *nostr.Pool WhitelistedPubKeys map[string]struct{} SeedRelays []string WotDepth int @@ -28,7 +28,7 @@ type SimpleInMemory struct { WotFetchTimeout int } -func NewSimpleInMemory(pool *nostr.SimplePool, whitelistedPubKeys map[string]struct{}, seedRelays []string, wotDepth int, minFollowers int, wotFetchTimeout int) *SimpleInMemory { +func NewSimpleInMemory(pool *nostr.Pool, whitelistedPubKeys map[string]struct{}, seedRelays []string, wotDepth int, minFollowers int, wotFetchTimeout int) *SimpleInMemory { return &SimpleInMemory{ Pool: pool, WhitelistedPubKeys: whitelistedPubKeys, @@ -97,13 +97,15 @@ func (wt *SimpleInMemory) Refresh(ctx context.Context) { defer cancel() filter := nostr.Filter{ - Authors: slices.Collect(maps.Keys(wt.WhitelistedPubKeys)), - Kinds: []int{nostr.KindFollowList}, + Kinds: []nostr.Kind{nostr.KindFollowList}, + } + for pubkeyHex := range wt.WhitelistedPubKeys { + filter.Authors = append(filter.Authors, nostr.MustPubKeyFromHex(pubkeyHex)) } slog.Info("πŸ›œ fetching Nostr events to build WoT") - events := wt.Pool.FetchMany(timeoutCtx, wt.SeedRelays, filter) + events := wt.Pool.FetchMany(timeoutCtx, wt.SeedRelays, filter, nostr.SubscriptionOptions{}) for ev := range latestEventByKindAndPubkey(timeoutCtx, events, &eventsAnalysed) { for contact := range ev.Tags.FindAll("p") { if len(contact) > 1 { @@ -127,16 +129,20 @@ func (wt *SimpleInMemory) Refresh(ctx context.Context) { processBatch := func(pubkeys []string) { timeoutCtx, cancel := context.WithTimeout(ctx, timeout) done := make(chan struct{}) + authors := make([]nostr.PubKey, 0, len(pubkeys)) + for _, pubkey := range pubkeys { + authors = append(authors, nostr.MustPubKeyFromHex(pubkey)) + } filter := nostr.Filter{ - Authors: pubkeys, - Kinds: []int{nostr.KindFollowList, nostr.KindRelayListMetadata}, + Authors: authors, + Kinds: []nostr.Kind{nostr.KindFollowList, nostr.KindRelayListMetadata}, } go func() { defer cancel() - events := wt.Pool.FetchMany(timeoutCtx, wt.SeedRelays, filter) + events := wt.Pool.FetchMany(timeoutCtx, wt.SeedRelays, filter, nostr.SubscriptionOptions{}) for ev := range latestEventByKindAndPubkey(timeoutCtx, events, &eventsAnalysed) { for contact := range ev.Tags.FindAll("p") { if len(contact) > 1 { diff --git a/policies.go b/policies.go index 58f00aee..ef1367ea 100644 --- a/policies.go +++ b/policies.go @@ -4,14 +4,14 @@ import ( "context" "log/slog" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/khatru" "github.com/barrydeen/haven/pkg/wot" - "github.com/fiatjaf/khatru" - "github.com/nbd-wtf/go-nostr" ) func MustBeWhitelistedToQuery(ctx context.Context, _ nostr.Filter) (bool, string) { - authenticatedUser := khatru.GetAuthed(ctx) - if _, ok := config.WhitelistedPubKeys[authenticatedUser]; !ok { + authenticatedUser, _ := khatru.GetAuthed(ctx) + if _, ok := config.WhitelistedPubKeys[authenticatedUser.Hex()]; !ok { slog.Debug("🚫 query rejected: user is not whitelisted", "user", authenticatedUser) return true, "restricted: you must be whitelisted to query this relay" } @@ -19,8 +19,8 @@ func MustBeWhitelistedToQuery(ctx context.Context, _ nostr.Filter) (bool, string } func MustBeInWotToQuery(ctx context.Context, _ nostr.Filter) (bool, string) { - authenticatedUser := khatru.GetAuthed(ctx) - if !wot.GetInstance().Has(ctx, authenticatedUser) { + authenticatedUser, _ := khatru.GetAuthed(ctx) + if !wot.GetInstance().Has(ctx, authenticatedUser.Hex()) { slog.Debug("🚫 query rejected: user is not in the web of trust", "user", authenticatedUser) return true, "restricted: you must be in the web of trust to query this relay" } @@ -29,14 +29,14 @@ func MustBeInWotToQuery(ctx context.Context, _ nostr.Filter) (bool, string) { func MustBeWhitelistedToPost(ctx context.Context, event *nostr.Event) (bool, string) { // Event from a whitelisted pubkey can always be posted, even if the user is not authenticated - if _, ok := config.WhitelistedPubKeys[event.PubKey]; ok { + if _, ok := config.WhitelistedPubKeys[event.PubKey.Hex()]; ok { return false, "" } - authenticatedUser := khatru.GetAuthed(ctx) - if authenticatedUser == "" { + authenticatedUser, ok := khatru.GetAuthed(ctx) + if !ok { return true, "auth-required: you must be authenticated to post to this relay" } - if _, ok := config.WhitelistedPubKeys[authenticatedUser]; !ok { + if _, ok := config.WhitelistedPubKeys[authenticatedUser.Hex()]; !ok { slog.Debug("🚫 event rejected: user is not whitelisted", "event", event.ID, "pubkey", authenticatedUser) return true, "restricted: you must be whitelisted to post to this relay" } @@ -45,14 +45,14 @@ func MustBeWhitelistedToPost(ctx context.Context, event *nostr.Event) (bool, str func MustBeInWotToPost(ctx context.Context, event *nostr.Event) (bool, string) { // Event from a pubkey in the WoT can always be posted, even if the user is not authenticated - if wot.GetInstance().Has(ctx, event.PubKey) { + if wot.GetInstance().Has(ctx, event.PubKey.Hex()) { return false, "" } - authenticatedUser := khatru.GetAuthed(ctx) - if authenticatedUser == "" { + authenticatedUser, ok := khatru.GetAuthed(ctx) + if !ok { return true, "auth-required: you must be authenticated to post to this relay" } - if !wot.GetInstance().Has(ctx, authenticatedUser) { + if !wot.GetInstance().Has(ctx, authenticatedUser.Hex()) { slog.Debug("🚫 event rejected: user is not in web of trust", "event", event.ID, "pubkey", authenticatedUser) return true, "you must be in the web of trust to post to this relay" } @@ -61,23 +61,23 @@ func MustBeInWotToPost(ctx context.Context, event *nostr.Event) (bool, string) { func MustNotBeBlacklistedToPost(ctx context.Context, event *nostr.Event) (bool, string) { // Events from a blacklisted pubkey ARE always rejected - if _, ok := config.BlacklistedPubKeys[event.PubKey]; ok { + if _, ok := config.BlacklistedPubKeys[event.PubKey.Hex()]; ok { slog.Debug("🚫 event rejected: event author is blacklisted", "event", event.ID, "pubkey", event.PubKey) return true, "you are blacklisted from this relay" } // Still need auth due to GiftWrap and other events with random pubkeys - authenticatedUser := khatru.GetAuthed(ctx) - if authenticatedUser == "" { + authenticatedUser, ok := khatru.GetAuthed(ctx) + if !ok { return true, "auth-required: you must be authenticated to post to this relay" } - if _, ok := config.BlacklistedPubKeys[authenticatedUser]; ok { + if _, ok := config.BlacklistedPubKeys[authenticatedUser.Hex()]; ok { slog.Debug("🚫 event rejected: authenticated user is blacklisted", "event", event.ID, "pubkey", authenticatedUser) return true, "you are blacklisted from this relay" } return false, "" } -var allowedChatKinds = map[int]struct{}{ +var allowedChatKinds = map[nostr.Kind]struct{}{ // Regular kinds nostr.KindSimpleGroupChatMessage: {}, nostr.KindSimpleGroupThreadedReply: {},