Add progressive throttle for follows ACL mode (v0.48.10)
Some checks failed
Go / build-and-release (push) Has been cancelled
Some checks failed
Go / build-and-release (push) Has been cancelled
- Add progressive throttle feature for follows ACL mode, allowing non-followed users to write with increasing delay instead of blocking - Delay increases linearly per event (default 200ms) and decays at 1:1 ratio with elapsed time, capping at configurable max (default 60s) - Track both IP and pubkey independently to prevent evasion - Add periodic cleanup to remove fully-decayed throttle entries - Fix BBolt serial resolver to return proper errors when buckets or entries are not found Files modified: - app/config/config.go: Add ORLY_FOLLOWS_THROTTLE_* env vars - app/handle-event.go: Apply throttle delay before event processing - app/listener.go: Add getFollowsThrottleDelay helper method - pkg/acl/follows.go: Integrate throttle with follows ACL - pkg/acl/follows_throttle.go: New progressive throttle implementation - pkg/bbolt/save-event.go: Return errors from serial lookups - pkg/version/version: Bump to v0.48.10 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,11 @@ type C struct {
|
||||
ClusterAdmins []string `env:"ORLY_CLUSTER_ADMINS" usage:"comma-separated list of npubs authorized to manage cluster membership"`
|
||||
FollowListFrequency time.Duration `env:"ORLY_FOLLOW_LIST_FREQUENCY" usage:"how often to fetch admin follow lists (default: 1h)" default:"1h"`
|
||||
|
||||
// Progressive throttle for follows ACL mode - allows non-followed users to write with increasing delay
|
||||
FollowsThrottleEnabled bool `env:"ORLY_FOLLOWS_THROTTLE" default:"false" usage:"enable progressive delay for non-followed users in follows ACL mode"`
|
||||
FollowsThrottlePerEvent time.Duration `env:"ORLY_FOLLOWS_THROTTLE_INCREMENT" default:"200ms" usage:"delay added per event for non-followed users"`
|
||||
FollowsThrottleMaxDelay time.Duration `env:"ORLY_FOLLOWS_THROTTLE_MAX" default:"60s" usage:"maximum throttle delay cap"`
|
||||
|
||||
// Blossom blob storage service level settings
|
||||
BlossomServiceLevels string `env:"ORLY_BLOSSOM_SERVICE_LEVELS" usage:"comma-separated list of service levels in format: name:storage_mb_per_sat_per_month (e.g., basic:1,premium:10)"`
|
||||
|
||||
@@ -841,3 +846,15 @@ func (cfg *C) GetNRCConfigValues() (
|
||||
cfg.NRCUseCashu,
|
||||
sessionTimeout
|
||||
}
|
||||
|
||||
// GetFollowsThrottleConfigValues returns the progressive throttle configuration values
|
||||
// for the follows ACL mode. This allows non-followed users to write with increasing delay.
|
||||
func (cfg *C) GetFollowsThrottleConfigValues() (
|
||||
enabled bool,
|
||||
perEvent time.Duration,
|
||||
maxDelay time.Duration,
|
||||
) {
|
||||
return cfg.FollowsThrottleEnabled,
|
||||
cfg.FollowsThrottlePerEvent,
|
||||
cfg.FollowsThrottleMaxDelay
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
@@ -254,6 +255,18 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
|
||||
}
|
||||
log.I.F("HandleEvent: authorized with access level %s", decision.AccessLevel)
|
||||
|
||||
// Progressive throttle for follows ACL mode (delays non-followed users)
|
||||
if delay := l.getFollowsThrottleDelay(env.E); delay > 0 {
|
||||
log.D.F("HandleEvent: applying progressive throttle delay of %v for %0x from %s",
|
||||
delay, env.E.Pubkey, l.remote)
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
return l.ctx.Err()
|
||||
case <-time.After(delay):
|
||||
// Delay completed, continue processing
|
||||
}
|
||||
}
|
||||
|
||||
// Route special event kinds (ephemeral, etc.) - use routing service
|
||||
if routeResult := l.eventRouter.Route(env.E, l.authedPubkey.Load()); routeResult.Action != routing.Continue {
|
||||
if routeResult.Action == routing.Handled {
|
||||
|
||||
@@ -301,6 +301,22 @@ func (l *Listener) getManagedACL() *database.ManagedACL {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFollowsThrottleDelay returns the progressive throttle delay for follows ACL mode.
|
||||
// Returns 0 if not in follows mode, throttle is disabled, or user is exempt.
|
||||
func (l *Listener) getFollowsThrottleDelay(ev *event.E) time.Duration {
|
||||
// Only applies to follows ACL mode
|
||||
if acl.Registry.Active.Load() != "follows" {
|
||||
return 0
|
||||
}
|
||||
// Find the Follows ACL instance and get the throttle delay
|
||||
for _, aclInstance := range acl.Registry.ACL {
|
||||
if follows, ok := aclInstance.(*acl.Follows); ok {
|
||||
return follows.GetThrottleDelay(ev.Pubkey, l.remote)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// QueryEvents queries events using the database QueryEvents method
|
||||
func (l *Listener) QueryEvents(ctx context.Context, f *filter.F) (event.S, error) {
|
||||
return l.DB.QueryEvents(ctx, f)
|
||||
|
||||
Reference in New Issue
Block a user