Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
f092d817c9
|
|||
|
c7eb532443
|
|||
|
e56b3f0083
|
|||
|
|
9064b3ab5f | ||
|
3486d3d4ab
|
|||
|
0ba555c6a8
|
10
.github/workflows/go.yml
vendored
10
.github/workflows/go.yml
vendored
@@ -75,11 +75,11 @@ jobs:
|
|||||||
mkdir -p release-binaries
|
mkdir -p release-binaries
|
||||||
|
|
||||||
# Build for different platforms
|
# Build for different platforms
|
||||||
GOEXPERIMENT=greenteagc,jsonv2 GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o release-binaries/orly-${VERSION}-linux-amd64 .
|
GOEXPERIMENT=greenteagc,jsonv2 GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -ldflags "-s -w" -o release-binaries/orly-${VERSION}-linux-amd64 .
|
||||||
GOEXPERIMENT=greenteagc,jsonv2 GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-linux-arm64 .
|
# GOEXPERIMENT=greenteagc,jsonv2 GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-linux-arm64 .
|
||||||
GOEXPERIMENT=greenteagc,jsonv2 GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-darwin-amd64 .
|
# GOEXPERIMENT=greenteagc,jsonv2 GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-darwin-amd64 .
|
||||||
GOEXPERIMENT=greenteagc,jsonv2 GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-darwin-arm64 .
|
# GOEXPERIMENT=greenteagc,jsonv2 GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-darwin-arm64 .
|
||||||
GOEXPERIMENT=greenteagc,jsonv2 GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-windows-amd64.exe .
|
# GOEXPERIMENT=greenteagc,jsonv2 GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-windows-amd64.exe .
|
||||||
|
|
||||||
# Note: Only building orly binary as requested
|
# Note: Only building orly binary as requested
|
||||||
# Other cmd utilities (aggregator, benchmark, convert, policytest, stresstest) are development tools
|
# Other cmd utilities (aggregator, benchmark, convert, policytest, stresstest) are development tools
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ func (l *Listener) HandleEvent(msg []byte) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.I.F("HandleEvent: continuing with event processing...")
|
|
||||||
if len(msg) > 0 {
|
if len(msg) > 0 {
|
||||||
log.I.F("extra '%s'", msg)
|
log.I.F("extra '%s'", msg)
|
||||||
}
|
}
|
||||||
|
|||||||
71
cmd/relay-tester/README.md
Normal file
71
cmd/relay-tester/README.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# relay-tester
|
||||||
|
|
||||||
|
A command-line tool for testing Nostr relay implementations against the NIP-01 specification and related NIPs.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
relay-tester -url <relay-url> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
- `-url` (required): Relay websocket URL (e.g., `ws://127.0.0.1:3334` or `wss://relay.example.com`)
|
||||||
|
- `-test <name>`: Run a specific test by name (default: run all tests)
|
||||||
|
- `-json`: Output results in JSON format
|
||||||
|
- `-v`: Verbose output (shows additional info for each test)
|
||||||
|
- `-list`: List all available tests and exit
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Run all tests against a local relay:
|
||||||
|
```bash
|
||||||
|
relay-tester -url ws://127.0.0.1:3334
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run all tests with verbose output:
|
||||||
|
```bash
|
||||||
|
relay-tester -url ws://127.0.0.1:3334 -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run a specific test:
|
||||||
|
```bash
|
||||||
|
relay-tester -url ws://127.0.0.1:3334 -test "Publishes basic event"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output results as JSON:
|
||||||
|
```bash
|
||||||
|
relay-tester -url ws://127.0.0.1:3334 -json
|
||||||
|
```
|
||||||
|
|
||||||
|
### List all available tests:
|
||||||
|
```bash
|
||||||
|
relay-tester -list
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exit Codes
|
||||||
|
|
||||||
|
- `0`: All required tests passed
|
||||||
|
- `1`: One or more required tests failed, or an error occurred
|
||||||
|
|
||||||
|
## Test Categories
|
||||||
|
|
||||||
|
The relay-tester runs tests covering:
|
||||||
|
|
||||||
|
- **Basic Event Operations**: Publishing, finding by ID/author/kind/tags
|
||||||
|
- **Filtering**: Time ranges, limits, multiple filters, scrape queries
|
||||||
|
- **Replaceable Events**: Metadata and contact list replacement
|
||||||
|
- **Parameterized Replaceable Events**: Addressable events with `d` tags
|
||||||
|
- **Event Deletion**: Deletion events (NIP-09)
|
||||||
|
- **Ephemeral Events**: Event handling for ephemeral kinds
|
||||||
|
- **EOSE Handling**: End of stored events signaling
|
||||||
|
- **Event Validation**: Signature verification, ID hash verification
|
||||||
|
- **JSON Compliance**: NIP-01 JSON escape sequences
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Tests are run in dependency order (some tests depend on others)
|
||||||
|
- Required tests must pass for the relay to be considered compliant
|
||||||
|
- Optional tests may fail without affecting overall compliance
|
||||||
|
- The tool connects to the relay using WebSocket and runs tests sequentially
|
||||||
|
|
||||||
160
cmd/relay-tester/main.go
Normal file
160
cmd/relay-tester/main.go
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"lol.mleku.dev/log"
|
||||||
|
relaytester "next.orly.dev/relay-tester"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
relayURL = flag.String("url", "", "relay websocket URL (required, e.g., ws://127.0.0.1:3334)")
|
||||||
|
testName = flag.String("test", "", "run specific test by name (default: run all tests)")
|
||||||
|
jsonOut = flag.Bool("json", false, "output results in JSON format")
|
||||||
|
verbose = flag.Bool("v", false, "verbose output")
|
||||||
|
listTests = flag.Bool("list", false, "list all available tests and exit")
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *listTests {
|
||||||
|
listAllTests()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *relayURL == "" {
|
||||||
|
log.E.F("required flag: -url (relay websocket URL)")
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate URL format
|
||||||
|
if !strings.HasPrefix(*relayURL, "ws://") && !strings.HasPrefix(*relayURL, "wss://") {
|
||||||
|
log.E.F("URL must start with ws:// or wss://")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test suite
|
||||||
|
if *verbose {
|
||||||
|
log.I.F("Creating test suite for %s...", *relayURL)
|
||||||
|
}
|
||||||
|
suite, err := relaytester.NewTestSuite(*relayURL)
|
||||||
|
if err != nil {
|
||||||
|
log.E.F("failed to create test suite: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
var results []relaytester.TestResult
|
||||||
|
if *testName != "" {
|
||||||
|
if *verbose {
|
||||||
|
log.I.F("Running test: %s", *testName)
|
||||||
|
}
|
||||||
|
result, err := suite.RunTest(*testName)
|
||||||
|
if err != nil {
|
||||||
|
log.E.F("failed to run test %s: %v", *testName, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
results = []relaytester.TestResult{result}
|
||||||
|
} else {
|
||||||
|
if *verbose {
|
||||||
|
log.I.F("Running all tests...")
|
||||||
|
}
|
||||||
|
if results, err = suite.Run(); err != nil {
|
||||||
|
log.E.F("failed to run tests: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output results
|
||||||
|
if *jsonOut {
|
||||||
|
jsonOutput, err := relaytester.FormatJSON(results)
|
||||||
|
if err != nil {
|
||||||
|
log.E.F("failed to format JSON: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println(jsonOutput)
|
||||||
|
} else {
|
||||||
|
outputResults(results, *verbose)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check exit code
|
||||||
|
hasRequiredFailures := false
|
||||||
|
for _, result := range results {
|
||||||
|
if result.Required && !result.Pass {
|
||||||
|
hasRequiredFailures = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasRequiredFailures {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputResults(results []relaytester.TestResult, verbose bool) {
|
||||||
|
passed := 0
|
||||||
|
failed := 0
|
||||||
|
requiredFailed := 0
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
if result.Pass {
|
||||||
|
passed++
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("PASS: %s", result.Name)
|
||||||
|
if result.Info != "" {
|
||||||
|
fmt.Printf(" - %s", result.Info)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
} else {
|
||||||
|
fmt.Printf("PASS: %s\n", result.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
if result.Required {
|
||||||
|
requiredFailed++
|
||||||
|
fmt.Printf("FAIL (required): %s", result.Name)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("FAIL (optional): %s", result.Name)
|
||||||
|
}
|
||||||
|
if result.Info != "" {
|
||||||
|
fmt.Printf(" - %s", result.Info)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Test Summary:")
|
||||||
|
fmt.Printf(" Total: %d\n", len(results))
|
||||||
|
fmt.Printf(" Passed: %d\n", passed)
|
||||||
|
fmt.Printf(" Failed: %d\n", failed)
|
||||||
|
fmt.Printf(" Required Failed: %d\n", requiredFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listAllTests() {
|
||||||
|
// Create a dummy test suite to get the list of tests
|
||||||
|
suite, err := relaytester.NewTestSuite("ws://127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
log.E.F("failed to create test suite: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Available tests:")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
testNames := suite.ListTests()
|
||||||
|
testInfo := suite.GetTestNames()
|
||||||
|
|
||||||
|
for _, name := range testNames {
|
||||||
|
required := ""
|
||||||
|
if testInfo[name] {
|
||||||
|
required = " (required)"
|
||||||
|
}
|
||||||
|
fmt.Printf(" - %s%s\n", name, required)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -285,16 +284,18 @@ func (p *P) CheckPolicy(access string, ev *event.E, loggedInPubkey []byte, ipAdd
|
|||||||
// Check if script is present and enabled
|
// Check if script is present and enabled
|
||||||
if rule.Script != "" && p.Manager != nil {
|
if rule.Script != "" && p.Manager != nil {
|
||||||
if p.Manager.IsEnabled() {
|
if p.Manager.IsEnabled() {
|
||||||
return p.checkScriptPolicy(access, ev, rule.Script, loggedInPubkey, ipAddress)
|
// Check if script file exists before trying to use it
|
||||||
|
if _, err := os.Stat(p.Manager.GetScriptPath()); err == nil {
|
||||||
|
// Script exists, try to use it
|
||||||
|
allowed, err := p.checkScriptPolicy(access, ev, rule.Script, loggedInPubkey, ipAddress)
|
||||||
|
if err == nil {
|
||||||
|
// Script ran successfully, return its decision
|
||||||
|
return allowed, nil
|
||||||
}
|
}
|
||||||
// Script is configured but policy is disabled - use default policy if rule has no other restrictions
|
// Script failed, fall through to apply other criteria
|
||||||
hasOtherRestrictions := len(rule.WriteAllow) > 0 || len(rule.WriteDeny) > 0 || len(rule.ReadAllow) > 0 || len(rule.ReadDeny) > 0 ||
|
log.W.F("policy script check failed for kind %d: %v, applying other criteria", ev.Kind, err)
|
||||||
rule.SizeLimit != nil || rule.ContentLimit != nil || len(rule.MustHaveTags) > 0 ||
|
}
|
||||||
rule.MaxExpiry != nil || rule.Privileged || rule.RateLimit != nil ||
|
// Script doesn't exist or failed, fall through to apply other criteria
|
||||||
rule.MaxAgeOfEvent != nil || rule.MaxAgeEventInFuture != nil
|
|
||||||
if !hasOtherRestrictions {
|
|
||||||
// No other restrictions, use default policy
|
|
||||||
return p.getDefaultPolicyAction(), nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,24 +482,14 @@ func (p *P) checkScriptPolicy(access string, ev *event.E, scriptPath string, log
|
|||||||
if !p.Manager.IsRunning() {
|
if !p.Manager.IsRunning() {
|
||||||
// Check if script file exists
|
// Check if script file exists
|
||||||
if _, err := os.Stat(p.Manager.GetScriptPath()); os.IsNotExist(err) {
|
if _, err := os.Stat(p.Manager.GetScriptPath()); os.IsNotExist(err) {
|
||||||
// Script doesn't exist, this is a fatal error
|
// Script doesn't exist, return error so caller can fall back to other criteria
|
||||||
buf := make([]byte, 1024*1024)
|
return false, fmt.Errorf("policy script does not exist at %s", p.Manager.GetScriptPath())
|
||||||
n := runtime.Stack(buf, true)
|
|
||||||
log.E.F("policy script does not exist at %s", p.Manager.GetScriptPath())
|
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: Policy script required but not found at %s\n", p.Manager.GetScriptPath())
|
|
||||||
fmt.Fprintf(os.Stderr, "Stack trace:\n%s\n", buf[:n])
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to start the policy and wait for it
|
// Try to start the policy and wait for it
|
||||||
if err := p.Manager.ensureRunning(); err != nil {
|
if err := p.Manager.ensureRunning(); err != nil {
|
||||||
// Startup failed, this is a fatal error
|
// Startup failed, return error so caller can fall back to other criteria
|
||||||
buf := make([]byte, 1024*1024)
|
return false, fmt.Errorf("failed to start policy script: %v", err)
|
||||||
n := runtime.Stack(buf, true)
|
|
||||||
log.E.F("failed to start policy script: %v", err)
|
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to start policy script: %v\n", err)
|
|
||||||
fmt.Fprintf(os.Stderr, "Stack trace:\n%s\n", buf[:n])
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v0.20.6
|
v0.21.4
|
||||||
@@ -18,6 +18,7 @@ type Client struct {
|
|||||||
url string
|
url string
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
subs map[string]chan []byte
|
subs map[string]chan []byte
|
||||||
|
complete map[string]bool // Track if subscription is complete (e.g., by ID)
|
||||||
okCh chan []byte // Channel for OK messages
|
okCh chan []byte // Channel for OK messages
|
||||||
countCh chan []byte // Channel for COUNT messages
|
countCh chan []byte // Channel for COUNT messages
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -39,6 +40,7 @@ func NewClient(url string) (c *Client, err error) {
|
|||||||
conn: conn,
|
conn: conn,
|
||||||
url: url,
|
url: url,
|
||||||
subs: make(map[string]chan []byte),
|
subs: make(map[string]chan []byte),
|
||||||
|
complete: make(map[string]bool),
|
||||||
okCh: make(chan []byte, 100),
|
okCh: make(chan []byte, 100),
|
||||||
countCh: make(chan []byte, 100),
|
countCh: make(chan []byte, 100),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -54,6 +56,11 @@ func (c *Client) Close() error {
|
|||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// URL returns the relay URL.
|
||||||
|
func (c *Client) URL() string {
|
||||||
|
return c.url
|
||||||
|
}
|
||||||
|
|
||||||
// Send sends a JSON message to the relay.
|
// Send sends a JSON message to the relay.
|
||||||
func (c *Client) Send(msg interface{}) (err error) {
|
func (c *Client) Send(msg interface{}) (err error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -109,8 +116,17 @@ func (c *Client) readLoop() {
|
|||||||
if len(raw) >= 2 {
|
if len(raw) >= 2 {
|
||||||
if subID, ok := raw[1].(string); ok {
|
if subID, ok := raw[1].(string); ok {
|
||||||
if ch, exists := c.subs[subID]; exists {
|
if ch, exists := c.subs[subID]; exists {
|
||||||
|
// Send EOSE message to channel
|
||||||
|
select {
|
||||||
|
case ch <- msg:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
// For complete subscriptions (by ID), close the channel after EOSE
|
||||||
|
if c.complete[subID] {
|
||||||
close(ch)
|
close(ch)
|
||||||
delete(c.subs, subID)
|
delete(c.subs, subID)
|
||||||
|
delete(c.complete, subID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +163,19 @@ func (c *Client) Subscribe(subID string, filters []interface{}) (ch chan []byte,
|
|||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
ch = make(chan []byte, 100)
|
ch = make(chan []byte, 100)
|
||||||
c.subs[subID] = ch
|
c.subs[subID] = ch
|
||||||
|
// Check if subscription is complete (has 'ids' filter)
|
||||||
|
isComplete := false
|
||||||
|
for _, f := range filters {
|
||||||
|
if fMap, ok := f.(map[string]interface{}); ok {
|
||||||
|
if ids, exists := fMap["ids"]; exists {
|
||||||
|
if idList, ok := ids.([]string); ok && len(idList) > 0 {
|
||||||
|
isComplete = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.complete[subID] = isComplete
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -165,6 +194,7 @@ func (c *Client) Unsubscribe(subID string) error {
|
|||||||
close(ch)
|
close(ch)
|
||||||
}()
|
}()
|
||||||
delete(c.subs, subID)
|
delete(c.subs, subID)
|
||||||
|
delete(c.complete, subID)
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return c.Send([]interface{}{"CLOSE", subID})
|
return c.Send([]interface{}{"CLOSE", subID})
|
||||||
@@ -269,7 +299,16 @@ func (c *Client) GetEvents(subID string, filters []interface{}, timeout time.Dur
|
|||||||
if err = json.Unmarshal(msg, &raw); err != nil {
|
if err = json.Unmarshal(msg, &raw); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(raw) >= 3 && raw[0] == "EVENT" {
|
if len(raw) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
typ, ok := raw[0].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case "EVENT":
|
||||||
|
if len(raw) >= 3 {
|
||||||
if evData, ok := raw[2].(map[string]interface{}); ok {
|
if evData, ok := raw[2].(map[string]interface{}); ok {
|
||||||
evJSON, _ := json.Marshal(evData)
|
evJSON, _ := json.Marshal(evData)
|
||||||
ev := event.New()
|
ev := event.New()
|
||||||
@@ -278,6 +317,10 @@ func (c *Client) GetEvents(subID string, filters []interface{}, timeout time.Dur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "EOSE":
|
||||||
|
// End of stored events - return what we have
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,6 +161,180 @@ func (s *TestSuite) registerTests() {
|
|||||||
Required: true,
|
Required: true,
|
||||||
Func: testSubscriptionClose,
|
Func: testSubscriptionClose,
|
||||||
},
|
},
|
||||||
|
// Filter tests
|
||||||
|
{
|
||||||
|
Name: "Since and until filters are inclusive",
|
||||||
|
Required: true,
|
||||||
|
Func: testSinceUntilAreInclusive,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Limit zero works",
|
||||||
|
Required: true,
|
||||||
|
Func: testLimitZero,
|
||||||
|
},
|
||||||
|
// Find tests
|
||||||
|
{
|
||||||
|
Name: "Events are ordered from newest to oldest",
|
||||||
|
Required: true,
|
||||||
|
Func: testEventsOrderedFromNewestToOldest,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Newest events are returned when filter is limited",
|
||||||
|
Required: true,
|
||||||
|
Func: testNewestEventsWhenLimited,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Finds by pubkey and kind",
|
||||||
|
Required: true,
|
||||||
|
Func: testFindByPubkeyAndKind,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Finds by pubkey and tags",
|
||||||
|
Required: true,
|
||||||
|
Func: testFindByPubkeyAndTags,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Finds by kind and tags",
|
||||||
|
Required: true,
|
||||||
|
Func: testFindByKindAndTags,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Finds by scrape",
|
||||||
|
Required: true,
|
||||||
|
Func: testFindByScrape,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
// Replaceable event tests
|
||||||
|
{
|
||||||
|
Name: "Replaces metadata",
|
||||||
|
Required: true,
|
||||||
|
Func: testReplacesMetadata,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Replaces contact list",
|
||||||
|
Required: true,
|
||||||
|
Func: testReplacesContactList,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Replaced events are still available by ID",
|
||||||
|
Required: false,
|
||||||
|
Func: testReplacedEventsStillAvailableByID,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Replaceable events replace older ones",
|
||||||
|
Required: true,
|
||||||
|
Func: testReplaceableEventRemovesPrevious,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Replaceable events rejected if a newer one exists",
|
||||||
|
Required: true,
|
||||||
|
Func: testReplaceableEventRejectedIfFuture,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Addressable events replace older ones",
|
||||||
|
Required: true,
|
||||||
|
Func: testAddressableEventRemovesPrevious,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Addressable events rejected if a newer one exists",
|
||||||
|
Required: true,
|
||||||
|
Func: testAddressableEventRejectedIfFuture,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
// Deletion tests
|
||||||
|
{
|
||||||
|
Name: "Deletes by a-tag address",
|
||||||
|
Required: true,
|
||||||
|
Func: testDeleteByAddr,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Delete by a-tag deletes older but not newer",
|
||||||
|
Required: true,
|
||||||
|
Func: testDeleteByAddrOnlyDeletesOlder,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Delete by a-tag is bound by a-tag",
|
||||||
|
Required: true,
|
||||||
|
Func: testDeleteByAddrIsBoundByTag,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
// Ephemeral tests
|
||||||
|
{
|
||||||
|
Name: "Ephemeral subscriptions work",
|
||||||
|
Required: false,
|
||||||
|
Func: testEphemeralSubscriptionsWork,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Persists ephemeral events",
|
||||||
|
Required: false,
|
||||||
|
Func: testPersistsEphemeralEvents,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
// EOSE tests
|
||||||
|
{
|
||||||
|
Name: "Supports EOSE",
|
||||||
|
Required: true,
|
||||||
|
Func: testSupportsEose,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Subscription receives event after ping period",
|
||||||
|
Required: true,
|
||||||
|
Func: testSubscriptionReceivesEventAfterPingPeriod,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Closes complete subscriptions after EOSE",
|
||||||
|
Required: false,
|
||||||
|
Func: testClosesCompleteSubscriptionsAfterEose,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Keeps open incomplete subscriptions after EOSE",
|
||||||
|
Required: true,
|
||||||
|
Func: testKeepsOpenIncompleteSubscriptionsAfterEose,
|
||||||
|
},
|
||||||
|
// JSON tests
|
||||||
|
{
|
||||||
|
Name: "Accepts events with empty tags",
|
||||||
|
Required: false,
|
||||||
|
Func: testAcceptsEventsWithEmptyTags,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Accepts NIP-01 JSON escape sequences",
|
||||||
|
Required: true,
|
||||||
|
Func: testAcceptsNip1JsonEscapeSequences,
|
||||||
|
Dependencies: []string{"Publishes basic event"},
|
||||||
|
},
|
||||||
|
// Registration tests
|
||||||
|
{
|
||||||
|
Name: "Sends OK after EVENT",
|
||||||
|
Required: true,
|
||||||
|
Func: testSendsOkAfterEvent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Verifies event signatures",
|
||||||
|
Required: true,
|
||||||
|
Func: testVerifiesSignatures,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Verifies event ID hashes",
|
||||||
|
Required: true,
|
||||||
|
Func: testVerifiesIdHashes,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range allTests {
|
for _, tc := range allTests {
|
||||||
s.AddTest(tc)
|
s.AddTest(tc)
|
||||||
@@ -251,6 +425,20 @@ func (s *TestSuite) GetResults() map[string]TestResult {
|
|||||||
return s.results
|
return s.results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListTests returns a list of all test names in execution order.
|
||||||
|
func (s *TestSuite) ListTests() []string {
|
||||||
|
return s.order
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTestNames returns all registered test names as a map (name -> required).
|
||||||
|
func (s *TestSuite) GetTestNames() map[string]bool {
|
||||||
|
result := make(map[string]bool)
|
||||||
|
for name, tc := range s.tests {
|
||||||
|
result[name] = tc.Required
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// FormatJSON formats results as JSON.
|
// FormatJSON formats results as JSON.
|
||||||
func FormatJSON(results []TestResult) (output string, err error) {
|
func FormatJSON(results []TestResult) (output string, err error) {
|
||||||
var data []byte
|
var data []byte
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -71,6 +71,9 @@ check_go_installation() {
|
|||||||
install_go() {
|
install_go() {
|
||||||
log_info "Installing Go $GO_VERSION..."
|
log_info "Installing Go $GO_VERSION..."
|
||||||
|
|
||||||
|
# Save original directory
|
||||||
|
local original_dir=$(pwd)
|
||||||
|
|
||||||
# Determine architecture
|
# Determine architecture
|
||||||
local arch=$(uname -m)
|
local arch=$(uname -m)
|
||||||
case $arch in
|
case $arch in
|
||||||
@@ -100,13 +103,17 @@ install_go() {
|
|||||||
rm -rf "$GOROOT"
|
rm -rf "$GOROOT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Extract Go
|
# Extract Go to a temporary location first, then move to final destination
|
||||||
log_info "Extracting Go to $GOROOT..."
|
log_info "Extracting Go..."
|
||||||
tar -xf "$go_archive"
|
tar -xf "$go_archive" -C /tmp
|
||||||
|
mv /tmp/go "$GOROOT"
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
rm -f "$go_archive"
|
rm -f "$go_archive"
|
||||||
|
|
||||||
|
# Return to original directory
|
||||||
|
cd "$original_dir"
|
||||||
|
|
||||||
log_success "Go $GO_VERSION installed successfully"
|
log_success "Go $GO_VERSION installed successfully"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +174,10 @@ build_application() {
|
|||||||
log_info "Updating embedded web assets..."
|
log_info "Updating embedded web assets..."
|
||||||
./scripts/update-embedded-web.sh
|
./scripts/update-embedded-web.sh
|
||||||
|
|
||||||
# The update-embedded-web.sh script should have built the binary
|
# Build the binary in the current directory
|
||||||
|
log_info "Building binary in current directory..."
|
||||||
|
CGO_ENABLED=1 go build -o "$BINARY_NAME"
|
||||||
|
|
||||||
if [[ -f "./$BINARY_NAME" ]]; then
|
if [[ -f "./$BINARY_NAME" ]]; then
|
||||||
log_success "ORLY relay built successfully"
|
log_success "ORLY relay built successfully"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,14 +1,40 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
apt -y install build-essential autoconf libtool git wget
|
|
||||||
cd $SCRIPT_DIR
|
# Update package lists
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
# Try to install from package manager first (much faster)
|
||||||
|
echo "Attempting to install secp256k1 from package manager..."
|
||||||
|
if apt-get install -y libsecp256k1-dev >/dev/null 2>&1; then
|
||||||
|
echo "✓ Installed secp256k1 from package manager"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fall back to building from source if package not available
|
||||||
|
echo "Package not available in repository, building from source..."
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
apt-get install -y build-essential autoconf automake libtool git wget pkg-config
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
rm -rf secp256k1
|
rm -rf secp256k1
|
||||||
|
|
||||||
|
# Clone and setup secp256k1
|
||||||
git clone https://github.com/bitcoin-core/secp256k1.git
|
git clone https://github.com/bitcoin-core/secp256k1.git
|
||||||
cd secp256k1
|
cd secp256k1
|
||||||
git checkout v0.6.0
|
git checkout v0.6.0
|
||||||
|
|
||||||
|
# Initialize and update submodules
|
||||||
git submodule init
|
git submodule init
|
||||||
git submodule update
|
git submodule update
|
||||||
|
|
||||||
|
# Build and install
|
||||||
./autogen.sh
|
./autogen.sh
|
||||||
./configure --enable-module-schnorrsig --enable-module-ecdh --prefix=/usr
|
./configure --enable-module-schnorrsig --enable-module-ecdh --prefix=/usr
|
||||||
make -j1
|
make -j$(nproc)
|
||||||
sudo make install
|
make install
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|||||||
Reference in New Issue
Block a user