refactor framework interface, simplify basic and whitelisted, bring expensive on and rewrite it.

This commit is contained in:
fiatjaf
2022-07-24 16:55:59 -03:00
parent 84f7d34840
commit aa96fa0a21
28 changed files with 743 additions and 369 deletions

1
expensive/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
relayer-expensive

2
expensive/Makefile Normal file
View File

@@ -0,0 +1,2 @@
relayer-expensive: $(shell find .. -name "*.go")
CC=$$(which musl-gcc) go build -ldflags='-s -w -linkmode external -extldflags "-static"' -o ./relayer-expensive

31
expensive/README.md Normal file
View File

@@ -0,0 +1,31 @@
expensive-relay, a sybil-free corner of nostr
=============================================
- a nostr relay implementation based on relayer.
- uses postgres, which I think must be over version 12 since it uses generated columns.
- requires users to manually register themselves to be able to publish events and pay a fee. this should prevent spam.
- aside from that it's basically the same thing as relayer basic.
running
-------
this requires a recent CLN version with Commando.
grab a binary from the releases page and run it with the following environment variables:
POSTGRESQL_DATABASE=postgresql://...
CLN_NODE_ID=02fed8723...
CLN_HOST=127.0.0.1:9735
CLN_RUNE=...
TICKET_PRICE_SATS=500
adjust the values above accordingly.
compiling
---------
if you know Go you already know this:
go install github.com/fiatjaf/relayer/expensive
or something like that.

68
expensive/handler.go Normal file
View File

@@ -0,0 +1,68 @@
package main
import (
"encoding/json"
"net/http"
)
func handleWebpage(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`
<meta charset=utf-8>
<title>expensive relay</title>
<h1>expensive relay</h1>
<a href="https://github.com/fiatjaf/expensive-relay">https://github.com/fiatjaf/expensive-relay</a>
<p>this is a nostr relay that only accepts events published from keys that pay a registration fee. this is an antispam measure. you can still be banned if you're spamming or doing something bad.</p>
<p>to register your nostr public key, type it below and click the button.</p>
<form>
<label>
nostr public key:
<input name=pubkey />
</label>
<button>Get Invoice</button>
</form>
<p id=message></p>
<a id=link><canvas id=qr /></a>
<code id=invoice></code>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js"></script>
<script>
document.querySelector('form').addEventListener('submit', async ev => {
ev.preventDefault()
let res = await (await fetch('/invoice?pubkey=' + ev.target.pubkey.value)).text()
let { bolt11, error } = JSON.parse(res)
if (bolt11) {
invoice.innerHTML = invoice
link.href = 'lightning:' + bolt11
new QRious({
element: qr,
value: bolt11.toUpperCase(),
size: 300
});
} else {
message.innerHTML = error
}
})
</script>
<style>
body {
margin: 10px auto;
width: 800px;
max-width: 90%;
}
</style>
`))
}
func handleInvoice(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
invoice, err := generateInvoice(r.URL.Query().Get("pubkey"))
if err != nil {
json.NewEncoder(w).Encode(struct {
Error string `json:"error"`
}{err.Error()})
} else {
json.NewEncoder(w).Encode(struct {
Invoice string `json:"bolt11"`
}{invoice})
}
}

78
expensive/lightning.go Normal file
View File

@@ -0,0 +1,78 @@
package main
import (
"encoding/json"
"errors"
"fmt"
lnsocket "github.com/jb55/lnsocket/go"
"github.com/tidwall/gjson"
)
func generateLabel(pubkey string) string { return fmt.Sprintf("relayer-expensive:ticket:%s", pubkey) }
func generateInvoice(pubkey string) (string, error) {
cln := lnsocket.LNSocket{}
cln.GenKey()
err := cln.ConnectAndInit(r.CLNHost, r.CLNNodeId)
if err != nil {
return "", err
}
defer cln.Disconnect()
// check if there is an invoice already
jparams, _ := json.Marshal(map[string]any{
"label": generateLabel(pubkey),
})
result, _ := cln.Rpc(r.CLNRune, "listinvoices", string(jparams))
if gjson.Get(result, "invoices.#").Int() == 1 {
return gjson.Get(result, "invoices.1.bolt11").String(), nil
}
// otherwise generate an invoice
jparams, _ = json.Marshal(map[string]any{
"amount_msat": r.TicketPriceSats * 1000,
"label": generateLabel(pubkey),
"description": fmt.Sprintf("%s's ticket for writing to relayer-expensive", pubkey),
})
result, err = cln.Rpc(r.CLNRune, "invoice", string(jparams))
if err != nil {
return "", err
}
resErr := gjson.Get(result, "error")
if resErr.Type != gjson.Null {
if resErr.Type == gjson.JSON {
return "", errors.New(resErr.Get("message").String())
} else if resErr.Type == gjson.String {
return "", errors.New(resErr.String())
}
return "", fmt.Errorf("Unknown commando error: '%v'", resErr)
}
invoice := gjson.Get(result, "result.bolt11")
if invoice.Type != gjson.String {
return "", fmt.Errorf("No bolt11 result found in invoice response, got %v", result)
}
return invoice.String(), nil
}
func checkInvoicePaidOk(pubkey string) bool {
cln := lnsocket.LNSocket{}
cln.GenKey()
err := cln.ConnectAndInit(r.CLNHost, r.CLNNodeId)
if err != nil {
return false
}
defer cln.Disconnect()
jparams, _ := json.Marshal(map[string]any{
"label": generateLabel(pubkey),
})
result, _ := cln.Rpc(r.CLNRune, "listinvoices", string(jparams))
return gjson.Get(result, "invoices.0.paid").String() == "paid"
}

84
expensive/main.go Normal file
View File

@@ -0,0 +1,84 @@
package main
import (
"encoding/json"
"fmt"
"time"
"github.com/fiatjaf/go-nostr"
"github.com/fiatjaf/relayer"
"github.com/fiatjaf/relayer/storage/postgresql"
"github.com/kelseyhightower/envconfig"
)
type Relay struct {
PostgresDatabase string `envconfig:"POSTGRESQL_DATABASE"`
CLNNodeId string `envconfig:"CLN_NODE_ID"`
CLNHost string `envconfig:"CLN_HOST"`
CLNRune string `envconfig:"CLN_RUNE"`
TicketPriceSats int64 `envconfig:"TICKET_PRICE_SATS"`
}
var r = &Relay{}
func (r *Relay) Name() string {
return "ExpensiveRelay"
}
func (r *Relay) Storage() relayer.Storage {
return &postgresql.PostgresBackend{DatabaseURL: r.PostgresDatabase}
}
func (r *Relay) Init() error {
err := envconfig.Process("", r)
if err != nil {
return fmt.Errorf("couldn't process envconfig: %w", err)
}
// every hour, delete all very old events
go func() {
db := r.Storage().(*postgresql.PostgresBackend)
for {
time.Sleep(60 * time.Minute)
db.DB.Exec(`DELETE FROM event WHERE created_at < $1`, time.Now().AddDate(0, -6, 0)) // 6 months
}
}()
// special handlers
relayer.Router.Path("/").HandlerFunc(handleWebpage)
relayer.Router.Path("/invoice").HandlerFunc(handleInvoice)
return nil
}
func (r *Relay) AcceptEvent(evt *nostr.Event) bool {
// only accept they have a good preimage for a paid invoice for their public key
if !checkInvoicePaidOk(evt.PubKey) {
return false
}
// block events that are too large
jsonb, _ := json.Marshal(evt)
if len(jsonb) > 100000 {
return false
}
return true
}
func (r *Relay) BeforeSave(evt *nostr.Event) {
// do nothing
}
func (r *Relay) AfterSave(evt *nostr.Event) {
// delete all but the 1000 most recent ones for each key
r.Storage().(*postgresql.PostgresBackend).DB.Exec(`DELETE FROM event WHERE pubkey = $1 AND kind = $2 AND created_at < (
SELECT created_at FROM event WHERE pubkey = $1
ORDER BY created_at DESC OFFSET 1000 LIMIT 1
)`, evt.PubKey, evt.Kind)
}
func main() {
relayer.Start(r)
}