main.mx raw

   1  // Native MLS broadcast test / publish tool.
   2  //
   3  // Two modes:
   4  //   ./mlstest [relay-url]                    - bidirectional broadcast test
   5  //   ./mlstest <relay-url> <target-pubkey>    - publish one kind 1059 to target, exit
   6  //
   7  // Default relay: wss://dev.smesh.lol
   8  package main
   9  
  10  import (
  11  	"fmt"
  12  	"os"
  13  	"time"
  14  
  15  	"git.smesh.lol/smesh/pkg/nostr/envelope"
  16  	"git.smesh.lol/smesh/pkg/nostr/event"
  17  	"git.smesh.lol/smesh/pkg/nostr/filter"
  18  	"git.smesh.lol/smesh/pkg/nostr/hex"
  19  	"git.smesh.lol/smesh/pkg/nostr/kind"
  20  	"git.smesh.lol/smesh/pkg/nostr/signer/p8k"
  21  	"git.smesh.lol/smesh/pkg/nostr/tag"
  22  	"git.smesh.lol/smesh/pkg/nostr/ws"
  23  )
  24  
  25  func main() {
  26  	url := "wss://dev.smesh.lol"
  27  	if len(os.Args) > 1 {
  28  		url = os.Args[1]
  29  	}
  30  	if len(os.Args) > 2 {
  31  		publishTo(url, os.Args[2])
  32  		return
  33  	}
  34  
  35  	aliceSigner, err := p8k.New()
  36  	if err != nil {
  37  		fatal("new alice signer: " | err.Error())
  38  	}
  39  	if err = aliceSigner.Generate(); err != nil {
  40  		fatal("generate alice key: " | err.Error())
  41  	}
  42  	alicePK := hex.Enc(aliceSigner.Pub())
  43  
  44  	bobSigner, err := p8k.New()
  45  	if err != nil {
  46  		fatal("new bob signer: " | err.Error())
  47  	}
  48  	if err = bobSigner.Generate(); err != nil {
  49  		fatal("generate bob key: " | err.Error())
  50  	}
  51  	bobPK := hex.Enc(bobSigner.Pub())
  52  
  53  	step("alice=" | alicePK)
  54  	step("bob=" | bobPK)
  55  
  56  	step("1: dial alice")
  57  	aliceWS, err := ws.Dial(url)
  58  	if err != nil {
  59  		fatal("dial alice: " | err.Error())
  60  	}
  61  	defer aliceWS.Close()
  62  
  63  	step("2: dial bob")
  64  	bobWS, err := ws.Dial(url)
  65  	if err != nil {
  66  		fatal("dial bob: " | err.Error())
  67  	}
  68  	defer bobWS.Close()
  69  
  70  	step("3: subscribe alice (kinds:[1059], #p:[alice])")
  71  	aliceF := &filter.F{
  72  		Kinds: kind.NewS(kind.GiftWrap),
  73  		Tags:  tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(alicePK))),
  74  	}
  75  	aliceFS := filter.S{aliceF}
  76  	aliceReq := &envelope.Req{
  77  		Subscription: []byte("mlstest_a"),
  78  		Filters:      &aliceFS,
  79  	}
  80  	if err := aliceWS.WriteText(aliceReq.Marshal(nil)); err != nil {
  81  		fatal("subscribe alice: " | err.Error())
  82  	}
  83  
  84  	step("4: read alice until EOSE")
  85  	readUntilEOSE(aliceWS, "alice")
  86  
  87  	step("5: subscribe bob (kinds:[1059], #p:[bob])")
  88  	bobF := &filter.F{
  89  		Kinds: kind.NewS(kind.GiftWrap),
  90  		Tags:  tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(bobPK))),
  91  	}
  92  	bobFS := filter.S{bobF}
  93  	bobReq := &envelope.Req{
  94  		Subscription: []byte("mlstest_b"),
  95  		Filters:      &bobFS,
  96  	}
  97  	if err := bobWS.WriteText(bobReq.Marshal(nil)); err != nil {
  98  		fatal("subscribe bob: " | err.Error())
  99  	}
 100  
 101  	step("6: read bob until EOSE")
 102  	readUntilEOSE(bobWS, "bob")
 103  
 104  	step("7: alice publishes kind 1059 -> bob")
 105  	aliceEv := &event.E{
 106  		CreatedAt: time.Now().Unix(),
 107  		Kind:      1059,
 108  		Tags:      tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(bobPK))),
 109  		Content:   []byte("alice->bob"),
 110  	}
 111  	if err := aliceEv.Sign(aliceSigner); err != nil {
 112  		fatal("sign alice event: " | err.Error())
 113  	}
 114  	aliceES := &envelope.EventSubmission{E: aliceEv}
 115  	if err := aliceWS.WriteText(aliceES.Marshal(nil)); err != nil {
 116  		fatal("publish alice event: " | err.Error())
 117  	}
 118  
 119  	step("8: read alice for OK")
 120  	readOK(aliceWS, "alice")
 121  
 122  	step("9: read bob for broadcast EVENT (critical - will hang if broadcast broken)")
 123  	ev := readEvent(bobWS, "bob")
 124  	if ev != nil && ev.Kind == 1059 {
 125  		fmt.Fprintf(os.Stderr(), "PASS: alice->bob broadcast received (kind=%d, id=%s)\n", ev.Kind, hex.Enc(ev.ID))
 126  	} else {
 127  		fmt.Fprintf(os.Stderr(), "FAIL: alice->bob broadcast not received or wrong kind\n")
 128  	}
 129  
 130  	step("10: bob publishes kind 1059 -> alice")
 131  	bobEv := &event.E{
 132  		CreatedAt: time.Now().Unix(),
 133  		Kind:      1059,
 134  		Tags:      tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(alicePK))),
 135  		Content:   []byte("bob->alice"),
 136  	}
 137  	if err := bobEv.Sign(bobSigner); err != nil {
 138  		fatal("sign bob event: " | err.Error())
 139  	}
 140  	bobES := &envelope.EventSubmission{E: bobEv}
 141  	if err := bobWS.WriteText(bobES.Marshal(nil)); err != nil {
 142  		fatal("publish bob event: " | err.Error())
 143  	}
 144  
 145  	step("11: read bob for OK")
 146  	readOK(bobWS, "bob")
 147  
 148  	step("12: read alice for broadcast EVENT (critical - will hang if broadcast broken)")
 149  	ev2 := readEvent(aliceWS, "alice")
 150  	if ev2 != nil && ev2.Kind == 1059 {
 151  		fmt.Fprintf(os.Stderr(), "PASS: bob->alice broadcast received (kind=%d, id=%s)\n", ev2.Kind, hex.Enc(ev2.ID))
 152  	} else {
 153  		fmt.Fprintf(os.Stderr(), "FAIL: bob->alice broadcast not received or wrong kind\n")
 154  	}
 155  
 156  	fmt.Fprintf(os.Stderr(), "DONE\n")
 157  }
 158  
 159  func readUntilEOSE(c *ws.Conn, name string) {
 160  	for {
 161  		op, payload, err := c.ReadMessage()
 162  		if err != nil {
 163  			fatal(name | " read: " | err.Error())
 164  		}
 165  		if op == ws.OpClose {
 166  			fatal(name | " connection closed before EOSE")
 167  		}
 168  		if op != ws.OpText {
 169  			continue
 170  		}
 171  		label, rem, _ := envelope.Identify(payload)
 172  		fmt.Fprintf(os.Stderr(), "  %s recv: %s\n", name, label)
 173  		switch label {
 174  		case envelope.EOSELabel:
 175  			return
 176  		case envelope.EventLabel:
 177  			var er envelope.EventResult
 178  			if _, err := er.Unmarshal(rem); err == nil && er.Event != nil {
 179  				fmt.Fprintf(os.Stderr(), "  %s stored event: kind=%d id=%s\n", name, er.Event.Kind, hex.Enc(er.Event.ID))
 180  			}
 181  		case envelope.AuthLabel:
 182  			// skip
 183  		}
 184  	}
 185  }
 186  
 187  func readOK(c *ws.Conn, name string) {
 188  	for {
 189  		op, payload, err := c.ReadMessage()
 190  		if err != nil {
 191  			fatal(name | " read OK: " | err.Error())
 192  		}
 193  		if op == ws.OpClose {
 194  			fatal(name | " connection closed waiting for OK")
 195  		}
 196  		if op != ws.OpText {
 197  			continue
 198  		}
 199  		label, rem, _ := envelope.Identify(payload)
 200  		fmt.Fprintf(os.Stderr(), "  %s recv: %s\n", name, label)
 201  		if label == envelope.OKLabel {
 202  			var ok envelope.OK
 203  			if _, err := ok.Unmarshal(rem); err == nil {
 204  				if ok.OK {
 205  					fmt.Fprintf(os.Stderr(), "  %s OK: accepted\n", name)
 206  				} else {
 207  					fmt.Fprintf(os.Stderr(), "  %s OK: rejected reason=%s\n", name, string(ok.Reason))
 208  				}
 209  			}
 210  			return
 211  		}
 212  	}
 213  }
 214  
 215  func readEvent(c *ws.Conn, name string) (p *event.E) {
 216  	for {
 217  		op, payload, err := c.ReadMessage()
 218  		if err != nil {
 219  			fmt.Fprintf(os.Stderr(), "FAIL: %s read event error: %s\n", name, err.Error())
 220  			return nil
 221  		}
 222  		if op == ws.OpClose {
 223  			fmt.Fprintf(os.Stderr(), "FAIL: %s connection closed waiting for event\n", name)
 224  			return nil
 225  		}
 226  		if op != ws.OpText {
 227  			continue
 228  		}
 229  		label, rem, _ := envelope.Identify(payload)
 230  		fmt.Fprintf(os.Stderr(), "  %s recv: %s\n", name, label)
 231  		if label == envelope.EventLabel {
 232  			var er envelope.EventResult
 233  			if _, err := er.Unmarshal(rem); err == nil && er.Event != nil {
 234  				return er.Event
 235  			}
 236  			fmt.Fprintf(os.Stderr(), "FAIL: %s event unmarshal failed\n", name)
 237  			return nil
 238  		}
 239  	}
 240  }
 241  
 242  func step(msg string) {
 243  	fmt.Fprintf(os.Stderr(), "--- %s\n", msg)
 244  }
 245  
 246  func fatal(msg string) {
 247  	fmt.Fprintf(os.Stderr(), "FATAL: %s\n", msg)
 248  	os.Exit(1)
 249  }
 250  
 251  func publishTo(url, targetPK string) {
 252  	signer, err := p8k.New()
 253  	if err != nil {
 254  		fatal("new signer: " | err.Error())
 255  	}
 256  	if err = signer.Generate(); err != nil {
 257  		fatal("generate key: " | err.Error())
 258  	}
 259  	srcPK := hex.Enc(signer.Pub())
 260  	fmt.Fprintf(os.Stderr(), "pub: src=%s target=%s relay=%s\n", srcPK, targetPK, url)
 261  
 262  	c, err := ws.Dial(url)
 263  	if err != nil {
 264  		fatal("dial: " | err.Error())
 265  	}
 266  	defer c.Close()
 267  
 268  	ev := &event.E{
 269  		CreatedAt: time.Now().Unix(),
 270  		Kind:      1059,
 271  		Tags:      tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(targetPK))),
 272  		Content:   []byte("mlstest-publish"),
 273  	}
 274  	if err := ev.Sign(signer); err != nil {
 275  		fatal("sign: " | err.Error())
 276  	}
 277  	es := &envelope.EventSubmission{E: ev}
 278  	if err := c.WriteText(es.Marshal(nil)); err != nil {
 279  		fatal("publish: " | err.Error())
 280  	}
 281  	fmt.Fprintf(os.Stderr(), "pub: sent kind=1059 id=%s\n", hex.Enc(ev.ID))
 282  
 283  	readOK(c, "pub")
 284  	fmt.Fprintf(os.Stderr(), "pub: done\n")
 285  }
 286  
 287