A framework for writing strfry's event-sifter (write policy) plugins in Go.
This project is formerly known as strfry-evsifter.
go get github.com/jiftechnify/strfrui
- Offers out-of-the-box event-sifters, including rate limiters.
- Sifter combinators: you can build own event-sifters by composing small parts together.
- Provides you foundations for writing a custom event-sifter as a simple function and running it.
The code below implements the same logic as this example using built-in event sifters in sifters
package:
package main
import (
"github.com/jiftechnify/strfrui"
"github.com/jiftechnify/strfrui/sifters"
)
var whiteList = []string{
"003ba9b2c5bd8afeed41a4ce362a8b7fc3ab59c25b6a1359cae9093f296dac01",
}
func main() {
// Initializing a strfrui.Runner with an event-sifter
// that accepts events from pubkeys in the whitelist.
// Then, start the sifting routine by calling Run().
strfrui.New(sifters.AuthorList(whiteList, sifters.Allow)).Run()
}
The complete list of available built-in sifters is here.
strfrui offers ways to compose multiple event-sifters together, called "combinators". They can be used to make a single complex sifter logic from small parts.
The code below shows the usage of these combinators:
package main
import (
"github.com/jiftechnify/strfrui"
"github.com/jiftechnify/strfrui/sifters"
)
var (
adminList = []string{"admin"}
blacklist = []string{"spammer", "scammer"}
)
func main() {
acceptAdmin := sifters.AuthorList(adminList, sifters.Allow)
rejectBlacklist := sifters.AuthorList(blacklist, sifters.Deny)
// sifters.WithMod() makes sifters modifiable.
// Sifter modification changes sifter's behavior within combinators.
// Here is an example of using OnlyIf() modifier.
// * base sifter says: event’s content must contain the word "nostr".
// * OnlyIf(...) says: restriction above applies to only kind 1 events.
nostrPostsOnly := sifters.WithMod(
sifters.ContentHasAnyWord([]string{"nostr"}, sifters.Allow)
).OnlyIf(sifters.KindList([]int{1}, sifters.Allow))
finalSifter := sifters. // finalSifter accepts if...
OneOf( // the input satisfies *one of* conditions:
acceptAdmin, // 1. author is the admin
sifters.Pipeline( // 2. the input satisfies *all* conditions:
rejectBlacklist, // a. author is not in the blacklist
nostrPostsOnly, // b. if kind == 1, its content must contain the word "nostr"
),
)
// run the finalSifter!
strfrui.New(finalSifter).Run()
}
The complete list of available combinators and modifiers is here.
You can easily set up a rate limiter to your Strfry relay by using built-in sifters under ratelimit
package!
Below is a brief example of how to apply a rate limiter:
package main
import (
"github.com/jiftechnify/strfrui"
"github.com/jiftechnify/strfrui/sifters/ratelimit"
)
func main() {
limiter := ratelimit.ByUser(
// every users can write 2 events per second, allowing burst up to 5 events.
ratelimit.QuotaPerSec(2).WithBurst(5),
// "users" are identified by pubkey. You can also use ratelimit.IPAddr here.
ratelimit.Pubkey,
).
// exclude all ephemeral events from rate limiting
Exclude(func(input *strfrui.Input) bool {
return sifters.KindsAllEphemeral(input.Event.Kind)
})
strfrui.New(limiter).Run()
}
You may want to use ratelimit.ByUserAndKind
to impose different limits for different event kinds.
limiter := ratelimit.ByUserAndKind([]ratelimit.QuotaForKinds{
// 2 events/s, burst up to 10 events for kind:1
ratelimit.QuotaPerSec(2).WithBurst(10).ForKinds(1),
// 5 events/s, burst up to 50 events for kind:7
ratelimit.QuotaPerSec(5).WithBurst(50).ForKinds(7),
}, ratelimit.Pubkey)
Essentially, event-sifter is just a function that takes an "input" (event + metadata of event source etc.) and returns "result" (action to take on the event: accept or reject).
type Sifter interface {
Sift (*strfrui.Input) (*strfrui.Result, error)
}
If you feel cumbersome to build sifters you want by combining small blocks, you can still implement overall sifter logic as a Go function. Of course, sifters written in such a way are also composable using the combinators!
The code below is a example of writing event-sifter as a function. The logic is equivalent to the sifter in the first example, but it adds custom logging.
package main
import (
"log"
"github.com/jiftechnify/strfrui"
)
var whitelist = map[string]struct{}{
"003ba9b2c5bd8afeed41a4ce362a8b7fc3ab59c25b6a1359cae9093f296dac01": {},
}
// event-sifting function
func acceptWhitelisted(input *strfrui.Input) (*strfrui.Result, error) {
if _, ok := whitelist[input.Event.PubKey]; ok {
return input.Accept()
}
// you can emit arbitrary logs by log.Print() family
log.Println("blocking event!")
return input.Reject("blocked: not on white-list")
}
func main() {
// note that we use *NewWithSifterFunc* here to set a sifting function
// instead of a Sifter interface implementation.
strfrui.NewWithSifterFunc(acceptWhitelisted).Run()
}
MIT