Compare commits

..

No commits in common. "57ca374de438cd04382854a68bd80df1ff423047" and "7d288cec1e0781e6daa407aea9c38ab8f763cb0a" have entirely different histories.

3 changed files with 130 additions and 177 deletions

View File

@ -1,85 +1,64 @@
mongodb:
uri: mongodb://localhost:27017/
database: grain
uri: "mongodb://localhost:27017/" # MongoDB connection URI
database: "grain" # Database name
server:
port: :8181
read_timeout: 10 # in seconds
write_timeout: 10 # in seconds
idle_timeout: 120 # in seconds
max_connections: 100
max_subscriptions_per_client: 10
rate_limit:
ws_limit: 100 # WebSocket messages per second
ws_burst: 200 # Allowed burst of WebSocket messages
event_limit: 50 # Events per second
event_burst: 100 # Allowed burst of events
req_limit: 50 # HTTP requests per second
req_burst: 100 # Allowed burst of HTTP requests
max_event_size: 51200 # Maximum size of an event in bytes
# Size limits for specific event kinds
kind_size_limits:
- kind: 0
max_size: 10240 # Maximum size in bytes for kind 0 events
- kind: 1
max_size: 25600 # Maximum size in bytes for kind 1 events
# Rate limits for different event categories
category_limits:
ephemeral:
kind: 0
limit: 100 # Events per second
burst: 200 # Allowed burst
parameterized_replaceable:
kind: 0
limit: 5
burst: 10
regular:
kind: 0
limit: 25
burst: 50
replaceable:
kind: 0
limit: 10
burst: 20
# Rate limits for specific event kinds
kind_limits:
- kind: 0
limit: 1 # Events per second
burst: 5 # Allowed burst
- kind: 1
limit: 25
burst: 50
- kind: 3
limit: 25
burst: 50
port: ":8080" # Port for the server to listen on
read_timeout: 10 # Read timeout in seconds
write_timeout: 10 # Write timeout in seconds
idle_timeout: 120 # Idle timeout in seconds
max_connections: 100 # Maximum number of concurrent connections
max_subscriptions_per_client: 10 # Maximum number of concurrent subscriptions per client
pubkey_whitelist:
enabled: false
pubkeys: [] # List of allowed public keys
npubs: [] # List of allowed npubs (Nostr public keys in bech32 format)
pubkeys: #["3fe0ab6cbdb7ee27148202249e3fb3b89423c6f6cda6ef43ea5057c3d93088e4",
#"cac0e43235806da094f0787a5b04e29ad04cb1a3c7ea5cf61edc1c338734082b"]
npubs: #["npub18ls2km9aklhzw9yzqgjfu0anhz2z83hkeknw7sl22ptu8kfs3rjq54am44"]
kind_whitelist:
enabled: false
kinds: [] # List of allowed event kinds
kinds: #[0, 1]
#If pubkey_whitelist not enabled, domain_whitelist will be ignored
domain_whitelist:
enabled: false
domains: [] # List of allowed domains
domains: #["happytavern.co", "nostrplebs.com"]
rate_limit:
ws_limit: 100 # Global rate limit for WebSocket messages (50 messages per second)
ws_burst: 200 # Global burst limit for WebSocket messages (allows a burst of 100 messages)
blacklist: #Removing a pubkey from the Blacklist requires a hard restart; Blacklist overides the Whitelist
enabled: true
permanent_ban_words: [] # Words that trigger a permanent ban
temp_ban_words: # Words that trigger a temporary ban
- crypto
- web3
- airdrop
max_temp_bans: 3 # Number of temporary bans before a permanent ban
temp_ban_duration: 3600 # Temporary ban duration in seconds
permanent_blacklist_pubkeys: # List of permanently banned public keys
- db0c9b8acd6101adb9b281c5321f98f6eebb33c5719d230ed1870997538a9765
permanent_blacklist_npubs: # List of permanently banned npubs
- npub1x0r5gflnk2mn6h3c70nvnywpy2j46gzqwg6k7uw6fxswyz0md9qqnhshtn
event_limit: 50 # Global rate limit for events (25 events per second)
event_burst: 100 # Global burst limit for events (allows a burst of 50 events)
req_limit: 50 # Added limit for REQ messages
req_burst: 100 # Added burst limit for REQ messages
max_event_size: 51200 # Global maximum event size in bytes (50Kb)
kind_size_limits:
- kind: 0
max_size: 10240 # Maximum event size for kind 0 in bytes (10Kb)
- kind: 1
max_size: 25600 # Maximum event size for kind 1 in bytes (25Kb)
category_limits: # Rate limits based on event categories
regular:
limit: 25 # Rate limit for regular events (50 events per second)
burst: 50 # Burst limit for regular events (allows a burst of 100 events)
replaceable:
limit: 10 # Rate limit for replaceable events (10 events per second)
burst: 20 # Burst limit for replaceable events (allows a burst of 20 events)
parameterized_replaceable:
limit: 5 # Rate limit for parameterized replaceable events (5 events per second)
burst: 10 # Burst limit for parameterized replaceable events (allows a burst of 10 events)
ephemeral:
limit: 100 # Rate limit for ephemeral events (100 events per second)
burst: 200 # Burst limit for ephemeral events (allows a burst of 200 events)
kind_limits: # Specific rate limits for different kinds of events
- kind: 0
limit: 1 # Rate limit for events of kind 0 (1 event per second)
burst: 5 # Burst limit for events of kind 0 (allows a burst of 5 events)
- kind: 1
limit: 25 # Rate limit for events of kind 1 (100 events per second)
burst: 50 # Burst limit for events of kind 1 (allows a burst of 200 events)
- kind: 3
limit: 25 # Rate limit for events of kind 3 (25 events per second)
burst: 50 # Burst limit for events of kind 3 (allows a burst of 50 events)

View File

@ -9,11 +9,6 @@ GRAIN is an open-source Nostr relay implementation written in Go. This project a
- **Dynamic Event Handling**: Capable of processing a wide range of events, categorized by type and kind, including support for event deletion as per NIP-09.
- **Configurable and Extensible**: Easily customizable through configuration files, with plans for future GUI-based configuration management to streamline server adjustments.
- **Efficient Rate Limiting**: Implements sophisticated rate limiting strategies to manage WebSocket messages, - events, and requests, ensuring fair resource allocation and protection against abuse.
- **Extensive Blacklist and Whitelist Functions**:
- Implements a robust blacklisting system with support for temporary and permanent bans based on content, pubkeys, or npubs.
- Features word-based content filtering for automatic temporary or permanent bans.
- Includes a configurable system for escalating temporary bans to permanent bans after a set number of violations.
- Offers whitelist capabilities for pubkeys, npubs, event kinds, and domains, allowing fine-grained control over permitted content and users.
- **Flexible Event Size Management**: Configurable size limits for events, with optional constraints based on - event kind, to maintain performance and prevent oversized data handling.
- **MongoDB Integration 🍃**: Utilizes MongoDB for high-performance storage and management of events, ensuring data integrity and efficient query capabilities.
- **Scalable Architecture**: Built with Go, leveraging its concurrency model to provide high throughput and scalability, suitable for handling large volumes of data and connections.
@ -25,7 +20,6 @@ GRAIN is an open-source Nostr relay implementation written in Go. This project a
### MongoDB Server 🍃
_I plan to add multiple other database types in the future (postgress, sqlite)_
GRAIN 🌾 leverages MongoDB for efficient storage and management of events. MongoDB, known for its high performance and scalability, is an ideal choice for handling large volumes of real-time data. GRAIN 🌾 uses MongoDB collections to store events categorized by kind and ensures quick retrieval and manipulation of these events through its robust querying capabilities.
You can get the free Community Server edition of MongoDB from the official MongoDB website:
@ -36,7 +30,7 @@ MongoDB provides extensive documentation and support to help you get started wit
Grain will automatically create the configurations and relay metadata files necessary if they do not already exist when you first run the program.
They are created in the root directory of Grain. You can change configurations and relay_metadata here and the server will automatically restart and use the new configurations. The relay must be restarted manually for new blacklist configurations to take effect.
They are created in the root directory of Grain. You can change configurations and relay_metadata here. The relay must be restarted for new configurations to take effect.
## Development

View File

@ -4,7 +4,6 @@ import (
"fmt"
"grain/config"
cfg "grain/config/types"
"log"
"os"
"strings"
"sync"
@ -13,6 +12,24 @@ import (
"gopkg.in/yaml.v2"
)
// Structure to manage temporary bans with timestamps
type tempBanEntry struct {
count int // Number of temporary bans
unbanTime time.Time // Time when the pubkey should be unbanned
}
var (
tempBannedPubkeys = make(map[string]*tempBanEntry)
mu sync.Mutex
)
func ClearTemporaryBans() {
mu.Lock()
defer mu.Unlock()
tempBannedPubkeys = make(map[string]*tempBanEntry)
}
// CheckBlacklist checks if a pubkey is in the blacklist based on event content
func CheckBlacklist(pubkey, eventContent string) (bool, string) {
cfg := config.GetConfig().Blacklist
@ -21,17 +38,13 @@ func CheckBlacklist(pubkey, eventContent string) (bool, string) {
return false, ""
}
log.Printf("Checking blacklist for pubkey: %s", pubkey)
// Check for permanent blacklist by pubkey or npub
if isPubKeyPermanentlyBlacklisted(pubkey) {
log.Printf("Pubkey %s is permanently blacklisted", pubkey)
return true, fmt.Sprintf("pubkey %s is permanently blacklisted", pubkey)
}
// Check for temporary ban
if isPubKeyTemporarilyBlacklisted(pubkey) {
log.Printf("Pubkey %s is temporarily blacklisted", pubkey)
return true, fmt.Sprintf("pubkey %s is temporarily blacklisted", pubkey)
}
@ -42,7 +55,7 @@ func CheckBlacklist(pubkey, eventContent string) (bool, string) {
if err != nil {
return true, fmt.Sprintf("pubkey %s is permanently banned and failed to save: %v", pubkey, err)
}
return true, "blocked: pubkey is permanently banned"
return true, fmt.Sprintf("pubkey %s is permanently banned for containing forbidden words", pubkey)
}
}
@ -53,7 +66,7 @@ func CheckBlacklist(pubkey, eventContent string) (bool, string) {
if err != nil {
return true, fmt.Sprintf("pubkey %s is temporarily banned and failed to save: %v", pubkey, err)
}
return true, "blocked: pubkey is temporarily banned"
return true, fmt.Sprintf("pubkey %s is temporarily banned for containing forbidden words", pubkey)
}
}
@ -68,36 +81,18 @@ func isPubKeyTemporarilyBlacklisted(pubkey string) bool {
entry, exists := tempBannedPubkeys[pubkey]
if !exists {
log.Printf("Pubkey %s not found in temporary blacklist", pubkey)
return false
}
now := time.Now()
if now.After(entry.unbanTime) {
log.Printf("Temporary ban for pubkey %s has expired. Count: %d", pubkey, entry.count)
// If the ban has expired, remove it from the temporary ban list
if time.Now().After(entry.unbanTime) {
delete(tempBannedPubkeys, pubkey)
return false
}
log.Printf("Pubkey %s is currently temporarily blacklisted. Count: %d, Unban time: %s", pubkey, entry.count, entry.unbanTime)
return true
}
func ClearTemporaryBans() {
mu.Lock()
defer mu.Unlock()
tempBannedPubkeys = make(map[string]*tempBanEntry)
}
var (
tempBannedPubkeys = make(map[string]*tempBanEntry)
mu sync.Mutex
)
type tempBanEntry struct {
count int
unbanTime time.Time
}
// Adds a pubkey to the temporary blacklist
func AddToTemporaryBlacklist(pubkey string) error {
mu.Lock()
@ -105,41 +100,24 @@ func AddToTemporaryBlacklist(pubkey string) error {
cfg := config.GetConfig().Blacklist
// Check if the pubkey is already temporarily banned
entry, exists := tempBannedPubkeys[pubkey]
if !exists {
log.Printf("Creating new temporary ban entry for pubkey %s", pubkey)
entry = &tempBanEntry{
count: 0,
unbanTime: time.Now(),
count: 1,
unbanTime: time.Now().Add(time.Duration(cfg.TempBanDuration) * time.Second),
}
tempBannedPubkeys[pubkey] = entry
} else {
log.Printf("Updating existing temporary ban entry for pubkey %s. Current count: %d", pubkey, entry.count)
if time.Now().After(entry.unbanTime) {
log.Printf("Previous ban for pubkey %s has expired. Keeping count at %d", pubkey, entry.count)
}
}
// Increment the count
// Increment the temporary ban count and set the unban time
entry.count++
entry.unbanTime = time.Now().Add(time.Duration(cfg.TempBanDuration) * time.Second)
log.Printf("Pubkey %s temporary ban count updated to: %d, MaxTempBans: %d, New unban time: %s", pubkey, entry.count, cfg.MaxTempBans, entry.unbanTime)
if entry.count > cfg.MaxTempBans {
log.Printf("Attempting to move pubkey %s to permanent blacklist", pubkey)
// If the count exceeds max_temp_bans, move to permanent blacklist
if entry.count >= cfg.MaxTempBans {
delete(tempBannedPubkeys, pubkey)
// Release the lock before calling AddToPermanentBlacklist
mu.Unlock()
err := AddToPermanentBlacklist(pubkey)
mu.Lock() // Re-acquire the lock
if err != nil {
log.Printf("Error adding pubkey %s to permanent blacklist: %v", pubkey, err)
return err
}
log.Printf("Successfully added pubkey %s to permanent blacklist", pubkey)
return AddToPermanentBlacklist(pubkey)
}
return nil
@ -176,7 +154,9 @@ func isPubKeyPermanentlyBlacklisted(pubKey string) bool {
}
func AddToPermanentBlacklist(pubkey string) error {
// Remove the mutex lock from here
mu.Lock()
defer mu.Unlock()
cfg := config.GetConfig().Blacklist
// Check if already blacklisted