mirror of
https://github.com/0ceanSlim/grain.git
synced 2024-11-23 17:07:13 +00:00
Compare commits
No commits in common. "57ca374de438cd04382854a68bd80df1ff423047" and "7d288cec1e0781e6daa407aea9c38ab8f763cb0a" have entirely different histories.
57ca374de4
...
7d288cec1e
@ -1,85 +1,64 @@
|
|||||||
mongodb:
|
mongodb:
|
||||||
uri: mongodb://localhost:27017/
|
uri: "mongodb://localhost:27017/" # MongoDB connection URI
|
||||||
database: grain
|
database: "grain" # Database name
|
||||||
|
|
||||||
server:
|
server:
|
||||||
port: :8181
|
port: ":8080" # Port for the server to listen on
|
||||||
read_timeout: 10 # in seconds
|
read_timeout: 10 # Read timeout in seconds
|
||||||
write_timeout: 10 # in seconds
|
write_timeout: 10 # Write timeout in seconds
|
||||||
idle_timeout: 120 # in seconds
|
idle_timeout: 120 # Idle timeout in seconds
|
||||||
max_connections: 100
|
max_connections: 100 # Maximum number of concurrent connections
|
||||||
max_subscriptions_per_client: 10
|
max_subscriptions_per_client: 10 # Maximum number of concurrent subscriptions per client
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
pubkey_whitelist:
|
pubkey_whitelist:
|
||||||
enabled: false
|
enabled: false
|
||||||
pubkeys: [] # List of allowed public keys
|
pubkeys: #["3fe0ab6cbdb7ee27148202249e3fb3b89423c6f6cda6ef43ea5057c3d93088e4",
|
||||||
npubs: [] # List of allowed npubs (Nostr public keys in bech32 format)
|
#"cac0e43235806da094f0787a5b04e29ad04cb1a3c7ea5cf61edc1c338734082b"]
|
||||||
|
npubs: #["npub18ls2km9aklhzw9yzqgjfu0anhz2z83hkeknw7sl22ptu8kfs3rjq54am44"]
|
||||||
kind_whitelist:
|
kind_whitelist:
|
||||||
enabled: false
|
enabled: false
|
||||||
kinds: [] # List of allowed event kinds
|
kinds: #[0, 1]
|
||||||
|
#If pubkey_whitelist not enabled, domain_whitelist will be ignored
|
||||||
domain_whitelist:
|
domain_whitelist:
|
||||||
enabled: false
|
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
|
event_limit: 50 # Global rate limit for events (25 events per second)
|
||||||
enabled: true
|
event_burst: 100 # Global burst limit for events (allows a burst of 50 events)
|
||||||
permanent_ban_words: [] # Words that trigger a permanent ban
|
req_limit: 50 # Added limit for REQ messages
|
||||||
temp_ban_words: # Words that trigger a temporary ban
|
req_burst: 100 # Added burst limit for REQ messages
|
||||||
- crypto
|
|
||||||
- web3
|
max_event_size: 51200 # Global maximum event size in bytes (50Kb)
|
||||||
- airdrop
|
kind_size_limits:
|
||||||
max_temp_bans: 3 # Number of temporary bans before a permanent ban
|
- kind: 0
|
||||||
temp_ban_duration: 3600 # Temporary ban duration in seconds
|
max_size: 10240 # Maximum event size for kind 0 in bytes (10Kb)
|
||||||
permanent_blacklist_pubkeys: # List of permanently banned public keys
|
- kind: 1
|
||||||
- db0c9b8acd6101adb9b281c5321f98f6eebb33c5719d230ed1870997538a9765
|
max_size: 25600 # Maximum event size for kind 1 in bytes (25Kb)
|
||||||
permanent_blacklist_npubs: # List of permanently banned npubs
|
|
||||||
- npub1x0r5gflnk2mn6h3c70nvnywpy2j46gzqwg6k7uw6fxswyz0md9qqnhshtn
|
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)
|
||||||
|
@ -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.
|
- **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.
|
- **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.
|
- **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.
|
- **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.
|
- **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.
|
- **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 🍃
|
### 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.
|
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:
|
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.
|
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
|
## Development
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"grain/config"
|
"grain/config"
|
||||||
cfg "grain/config/types"
|
cfg "grain/config/types"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -13,6 +12,24 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"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
|
// CheckBlacklist checks if a pubkey is in the blacklist based on event content
|
||||||
func CheckBlacklist(pubkey, eventContent string) (bool, string) {
|
func CheckBlacklist(pubkey, eventContent string) (bool, string) {
|
||||||
cfg := config.GetConfig().Blacklist
|
cfg := config.GetConfig().Blacklist
|
||||||
@ -21,17 +38,13 @@ func CheckBlacklist(pubkey, eventContent string) (bool, string) {
|
|||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Checking blacklist for pubkey: %s", pubkey)
|
|
||||||
|
|
||||||
// Check for permanent blacklist by pubkey or npub
|
// Check for permanent blacklist by pubkey or npub
|
||||||
if isPubKeyPermanentlyBlacklisted(pubkey) {
|
if isPubKeyPermanentlyBlacklisted(pubkey) {
|
||||||
log.Printf("Pubkey %s is permanently blacklisted", pubkey)
|
|
||||||
return true, fmt.Sprintf("pubkey %s is permanently blacklisted", pubkey)
|
return true, fmt.Sprintf("pubkey %s is permanently blacklisted", pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for temporary ban
|
// Check for temporary ban
|
||||||
if isPubKeyTemporarilyBlacklisted(pubkey) {
|
if isPubKeyTemporarilyBlacklisted(pubkey) {
|
||||||
log.Printf("Pubkey %s is temporarily blacklisted", pubkey)
|
|
||||||
return true, fmt.Sprintf("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 {
|
if err != nil {
|
||||||
return true, fmt.Sprintf("pubkey %s is permanently banned and failed to save: %v", pubkey, err)
|
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 {
|
if err != nil {
|
||||||
return true, fmt.Sprintf("pubkey %s is temporarily banned and failed to save: %v", pubkey, err)
|
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]
|
entry, exists := tempBannedPubkeys[pubkey]
|
||||||
if !exists {
|
if !exists {
|
||||||
log.Printf("Pubkey %s not found in temporary blacklist", pubkey)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
// If the ban has expired, remove it from the temporary ban list
|
||||||
if now.After(entry.unbanTime) {
|
if time.Now().After(entry.unbanTime) {
|
||||||
log.Printf("Temporary ban for pubkey %s has expired. Count: %d", pubkey, entry.count)
|
delete(tempBannedPubkeys, pubkey)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Pubkey %s is currently temporarily blacklisted. Count: %d, Unban time: %s", pubkey, entry.count, entry.unbanTime)
|
|
||||||
return true
|
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
|
// Adds a pubkey to the temporary blacklist
|
||||||
func AddToTemporaryBlacklist(pubkey string) error {
|
func AddToTemporaryBlacklist(pubkey string) error {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@ -105,41 +100,24 @@ func AddToTemporaryBlacklist(pubkey string) error {
|
|||||||
|
|
||||||
cfg := config.GetConfig().Blacklist
|
cfg := config.GetConfig().Blacklist
|
||||||
|
|
||||||
|
// Check if the pubkey is already temporarily banned
|
||||||
entry, exists := tempBannedPubkeys[pubkey]
|
entry, exists := tempBannedPubkeys[pubkey]
|
||||||
if !exists {
|
if !exists {
|
||||||
log.Printf("Creating new temporary ban entry for pubkey %s", pubkey)
|
|
||||||
entry = &tempBanEntry{
|
entry = &tempBanEntry{
|
||||||
count: 0,
|
count: 1,
|
||||||
unbanTime: time.Now(),
|
unbanTime: time.Now().Add(time.Duration(cfg.TempBanDuration) * time.Second),
|
||||||
}
|
}
|
||||||
tempBannedPubkeys[pubkey] = entry
|
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.count++
|
||||||
entry.unbanTime = time.Now().Add(time.Duration(cfg.TempBanDuration) * time.Second)
|
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 the count exceeds max_temp_bans, move to permanent blacklist
|
||||||
|
if entry.count >= cfg.MaxTempBans {
|
||||||
if entry.count > cfg.MaxTempBans {
|
|
||||||
log.Printf("Attempting to move pubkey %s to permanent blacklist", pubkey)
|
|
||||||
delete(tempBannedPubkeys, pubkey)
|
delete(tempBannedPubkeys, pubkey)
|
||||||
|
return AddToPermanentBlacklist(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 nil
|
return nil
|
||||||
@ -176,7 +154,9 @@ func isPubKeyPermanentlyBlacklisted(pubKey string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddToPermanentBlacklist(pubkey string) error {
|
func AddToPermanentBlacklist(pubkey string) error {
|
||||||
// Remove the mutex lock from here
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
cfg := config.GetConfig().Blacklist
|
cfg := config.GetConfig().Blacklist
|
||||||
|
|
||||||
// Check if already blacklisted
|
// Check if already blacklisted
|
||||||
|
Loading…
Reference in New Issue
Block a user