decred.org/dcrdex@v1.0.5/tatanka/cmd/demo/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "net" 10 "os" 11 "os/signal" 12 "os/user" 13 "path/filepath" 14 "sync" 15 "time" 16 17 "decred.org/dcrdex/dex" 18 dexbtc "decred.org/dcrdex/dex/networks/btc" 19 "decred.org/dcrdex/server/comms" 20 "decred.org/dcrdex/tatanka" 21 "decred.org/dcrdex/tatanka/chain/utxo" 22 tankaclient "decred.org/dcrdex/tatanka/client" 23 "decred.org/dcrdex/tatanka/mj" 24 "decred.org/dcrdex/tatanka/tanka" 25 "decred.org/dcrdex/tatanka/tcp" 26 "github.com/decred/dcrd/dcrec/secp256k1/v4" 27 ) 28 29 var ( 30 logMaker *dex.LoggerMaker 31 usr, _ = user.Current() 32 dextestDir = filepath.Join(usr.HomeDir, "dextest") 33 decredRPCPath = filepath.Join(dextestDir, "dcr", "alpha", "rpc.cert") 34 chains = []tatanka.ChainConfig{ 35 { 36 Symbol: "dcr", 37 Config: mustEncode(&utxo.DecredConfigFile{ 38 RPCUser: "user", 39 RPCPass: "pass", 40 RPCListen: "127.0.0.1:19561", 41 RPCCert: decredRPCPath, 42 }), 43 }, 44 { 45 Symbol: "btc", 46 Config: mustEncode(&utxo.BitcoinConfigFile{ 47 RPCConfig: dexbtc.RPCConfig{ 48 RPCUser: "user", 49 RPCPass: "pass", 50 RPCBind: "127.0.0.1", 51 RPCPort: 20556, 52 }, 53 }), 54 }, 55 } 56 ) 57 58 func main() { 59 if err := mainErr(); err != nil { 60 fmt.Fprintln(os.Stderr, err) 61 } 62 } 63 64 func mainErr() (err error) { 65 var wg sync.WaitGroup 66 defer func() { 67 wg.Wait() 68 }() 69 70 ctx, cancel := context.WithCancel(context.Background()) 71 defer cancel() 72 73 killChan := make(chan os.Signal, 1) 74 signal.Notify(killChan, os.Interrupt) 75 go func() { 76 <-killChan 77 fmt.Println("Shutting down...") 78 cancel() 79 }() 80 81 tmpDir, _ := os.MkdirTemp("", "") 82 defer os.RemoveAll(tmpDir) 83 84 logMaker, err = dex.NewLoggerMaker(os.Stdout, dex.LevelTrace.String()) 85 if err != nil { 86 return fmt.Errorf("failed to create custom logger: %w", err) 87 } 88 89 comms.UseLogger(logMaker.NewLogger("COMMS", dex.LevelTrace)) 90 91 addrs, err := findOpenAddrs(2) 92 if err != nil { 93 return fmt.Errorf("findOpenAddrs error: %w", err) 94 } 95 96 genKey := func(dir string) (*secp256k1.PrivateKey, tanka.PeerID) { 97 priv, _ := secp256k1.GeneratePrivateKey() 98 var peerID tanka.PeerID 99 copy(peerID[:], priv.PubKey().SerializeCompressed()) 100 os.MkdirAll(dir, 0755) 101 os.WriteFile(filepath.Join(dir, "priv.key"), priv.Serialize(), 0644) 102 return priv, peerID 103 } 104 dir0 := filepath.Join(tmpDir, "tatanka1") 105 priv0, pid0 := genKey(dir0) 106 dir1 := filepath.Join(tmpDir, "tatanka2") 107 priv1, pid1 := genKey(dir1) 108 109 wg.Add(1) 110 go func() { 111 defer wg.Done() 112 defer cancel() 113 114 runServer(ctx, dir0, addrs[0], addrs[1], priv1.PubKey().SerializeCompressed(), true) 115 }() 116 117 time.Sleep(time.Second) 118 119 wg.Add(1) 120 go func() { 121 defer wg.Done() 122 defer cancel() 123 124 runServer(ctx, dir1, addrs[1], addrs[0], priv0.PubKey().SerializeCompressed(), true) 125 }() 126 127 time.Sleep(time.Second) 128 129 cl1, err := newClient(ctx, addrs[0].String(), pid0, 0) 130 if err != nil { 131 return fmt.Errorf("error making first connected client: %v", err) 132 } 133 134 cl2, err := newClient(ctx, addrs[1].String(), pid1, 1) 135 if err != nil { 136 return fmt.Errorf("error making second connected client: %v", err) 137 } 138 139 // cm.Disconnect() 140 141 if err := cl1.SubscribeMarket(42, 0); err != nil { 142 return fmt.Errorf("SubscribeMarket error: %v", err) 143 } 144 145 // Miss the relayed new subscription broadcast. 146 time.Sleep(time.Second) 147 148 if err := cl2.SubscribeMarket(42, 0); err != nil { 149 return fmt.Errorf("SubscribeMarket error: %v", err) 150 } 151 152 // Client 1 should receive a notification. 153 select { 154 case bcastI := <-cl1.Next(): 155 bcast, is := bcastI.(mj.Broadcast) 156 if !is { 157 return fmt.Errorf("expected new subscription Broadcast, got %T", bcastI) 158 } 159 if bcast.MessageType != mj.MessageTypeNewSubscriber { 160 return fmt.Errorf("expected new subscription message type, got %s", bcast.MessageType) 161 } 162 fmt.Printf("Client 1's bounced broadcast received: %+v \n", bcastI) 163 case <-time.After(time.Second): 164 return errors.New("timed out waiting for client 1 to receive new subscriber notification") 165 } 166 167 mktName, err := dex.MarketName(42, 0) 168 if err != nil { 169 return fmt.Errorf("error constructing market name: %w", err) 170 } 171 172 if err := cl1.Broadcast(mj.TopicMarket, tanka.Subject(mktName), mj.MessageTypeTrollBox, &mj.Troll{Msg: "trollin'"}); err != nil { 173 return fmt.Errorf("broadcast error: %w", err) 174 } 175 176 select { 177 case bcastI := <-cl1.Next(): 178 bcast, is := bcastI.(mj.Broadcast) 179 if !is { 180 return fmt.Errorf("client 1 expected trollbox Broadcast bounceback, got %T", bcastI) 181 } 182 if bcast.MessageType != mj.MessageTypeTrollBox { 183 return fmt.Errorf("client 1 expected trollbox message type for bounced message, got %s", bcast.MessageType) 184 } 185 fmt.Printf("Client 1's bounced broadcast received: %+v \n", bcastI) 186 case <-time.After(time.Second): 187 return errors.New("timed out waiting for client 1 broadcast to bounce back") 188 } 189 190 select { 191 case bcastI := <-cl2.Next(): 192 bcast, is := bcastI.(mj.Broadcast) 193 if !is { 194 return fmt.Errorf("client 2 expected trollbox Broadcast, got %T", bcastI) 195 } 196 if bcast.MessageType != mj.MessageTypeTrollBox { 197 return fmt.Errorf("client 2 expected trollbox message type, got %s", bcast.MessageType) 198 } 199 fmt.Printf("Client 2's received the broadcast: %+v \n", bcastI) 200 case <-time.After(time.Second): 201 return errors.New("timed out waiting for client 2 to receive broadcast from client 1") 202 } 203 204 // Connect clients 205 if _, err := cl1.ConnectPeer(cl2.ID()); err != nil { 206 return fmt.Errorf("error connecting peers: %w", err) 207 } 208 209 select { 210 case newPeerI := <-cl2.Next(): 211 if _, is := newPeerI.(*tankaclient.IncomingPeerConnect); !is { 212 return fmt.Errorf("expected IncomingPeerConnect, got %T", newPeerI) 213 } 214 fmt.Printf("Client 2's received the new peer notification: %+v \n", newPeerI) 215 case <-time.After(time.Second): 216 return errors.New("timed out waiting for client 2 to receive broadcast from client 1") 217 } 218 219 // Send a tankagram 220 const testRoute = "test_route" 221 respC := make(chan interface{}) 222 go func() { 223 msg := mj.MustRequest(testRoute, true) 224 r, resB, err := cl1.SendTankagram(cl2.ID(), msg) 225 if r.Result != mj.TRTTransmitted { 226 respC <- fmt.Errorf("not transmitted. %q", r.Result) 227 return 228 } 229 if err != nil { 230 respC <- err 231 return 232 } 233 respC <- resB 234 }() 235 236 select { 237 case gramI := <-cl2.Next(): 238 gram, is := gramI.(*tankaclient.IncomingTankagram) 239 if !is { 240 return fmt.Errorf("expected IncomingTankagram, got a %T", gramI) 241 } 242 if gram.Msg.Route != testRoute || !bytes.Equal(gram.Msg.Payload, []byte("true")) { 243 return fmt.Errorf("tankagram ain't right %s, %s", gram.Msg.Route, string(gram.Msg.Payload)) 244 } 245 gram.Respond("ok") 246 case <-time.After(time.Second): 247 return errors.New("timed out waiting for tankagram from client 1") 248 } 249 250 select { 251 case respI := <-respC: 252 switch resp := respI.(type) { 253 case error: 254 return fmt.Errorf("error sending tankagram: %v", resp) 255 case json.RawMessage: 256 var s string 257 if err := json.Unmarshal(resp, &s); err != nil { 258 return fmt.Errorf("tankagram response didn't unmarshal: %w", err) 259 } 260 if s != "ok" { 261 return fmt.Errorf("wrong tankagram response %q", s) 262 } 263 } 264 case <-time.After(time.Second): 265 return errors.New("timed out waiting for SendTankagram to return") 266 } 267 268 // Fiat rate live test requires internet connection. 269 fmt.Println("Testing fiat rates...") 270 if err := cl1.SubscribeToFiatRates(); err != nil { 271 return err 272 } 273 274 // Wait for rate message. 275 <-cl1.Next() 276 277 want := len(chains) 278 got := 0 279 for _, c := range chains { 280 assetID, _ := dex.BipSymbolID(c.Symbol) 281 fiatRate := cl1.FiatRate(assetID) // check if rate has been cached. 282 if fiatRate != 0 { 283 got++ 284 fmt.Printf("\nRate found for %s\n", dex.BipIDSymbol(assetID)) 285 } 286 } 287 288 if got < want { 289 fmt.Printf("\nGot fiat rates for %d out of %d assets\n", got, want) 290 } 291 292 fmt.Println("!!!!!!!! Test Success !!!!!!!!") 293 294 cancel() 295 296 wg.Wait() 297 298 cl1.cm.Wait() 299 cl2.cm.Wait() 300 301 return nil 302 } 303 304 type connectedClient struct { 305 *tankaclient.TankaClient 306 cm *dex.ConnectionMaster 307 } 308 309 func findOpenAddrs(n int) ([]net.Addr, error) { 310 addrs := make([]net.Addr, 0, n) 311 for i := 0; i < n; i++ { 312 addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 313 if err != nil { 314 return nil, err 315 } 316 317 l, err := net.ListenTCP("tcp", addr) 318 if err != nil { 319 return nil, err 320 } 321 defer l.Close() 322 addrs = append(addrs, l.Addr()) 323 } 324 325 return addrs, nil 326 } 327 328 func runServer(ctx context.Context, dir string, addr, peerAddr net.Addr, peerID []byte, disableMessariFiatRateSource bool) { 329 n := newBootNode(peerAddr.String(), peerID) 330 331 log := logMaker.Logger(fmt.Sprintf("SRV[%s]", addr)) 332 333 os.MkdirAll(dir, 0755) 334 335 ttCfg := &tatanka.ConfigFile{Chains: chains} 336 rawCfg, _ := json.Marshal(ttCfg) 337 cfgPath := filepath.Join(dir, "config.json") 338 if err := os.WriteFile(cfgPath, rawCfg, 0644); err != nil { 339 log.Errorf("WriteFile error: %v", err) 340 return 341 } 342 343 cfg := &tatanka.Config{ 344 Net: dex.Simnet, 345 DataDir: dir, 346 Logger: log, 347 RPC: comms.RPCConfig{ 348 ListenAddrs: []string{addr.String()}, 349 NoTLS: true, 350 }, 351 ConfigPath: cfgPath, 352 WhiteList: []tatanka.BootNode{n}, 353 } 354 355 if disableMessariFiatRateSource { 356 // Requesting multiple rates from the same IP can trigger a 429 HTTP 357 // error. 358 cfg.FiatOracleConfig.DisabledFiatSources = "Messari" 359 } 360 361 t, err := tatanka.New(cfg) 362 if err != nil { 363 log.Errorf("error creating Tatanka node: %v", err) 364 return 365 } 366 367 cm := dex.NewConnectionMaster(t) 368 if err := cm.ConnectOnce(ctx); err != nil { 369 log.Errorf("ConnectOnce error: %v", err) 370 return 371 } 372 373 cm.Wait() 374 } 375 376 func newBootNode(addr string, peerID []byte) tatanka.BootNode { 377 tcpCfg, _ := json.Marshal(&tcp.RemoteNodeConfig{ 378 URL: "ws://" + addr, 379 // Cert: , 380 }) 381 return tatanka.BootNode{ 382 Protocol: "ws", 383 Config: tcpCfg, 384 PeerID: peerID, 385 } 386 } 387 388 func newClient(ctx context.Context, addr string, peerID tanka.PeerID, i int) (*connectedClient, error) { 389 log := logMaker.NewLogger(fmt.Sprintf("tCL[%d:%s]", i, addr), dex.LevelTrace) 390 priv, _ := secp256k1.GeneratePrivateKey() 391 392 tc := tankaclient.New(&tankaclient.Config{ 393 Logger: log.SubLogger("tTC"), 394 PrivateKey: priv, 395 }) 396 397 cm := dex.NewConnectionMaster(tc) 398 if err := cm.ConnectOnce(ctx); err != nil { 399 return nil, fmt.Errorf("ConnectOnce error: %w", err) 400 } 401 402 if err := tc.AddTatankaNode(ctx, peerID, "ws://"+addr, nil); err != nil { 403 cm.Disconnect() 404 return nil, fmt.Errorf("error adding server %q", addr) 405 } 406 407 if err := tc.PostBond(&tanka.Bond{ 408 PeerID: tc.ID(), 409 AssetID: 42, 410 CoinID: nil, 411 Strength: 1, 412 Expiration: time.Now().Add(time.Hour * 24 * 365), 413 }); err != nil { 414 cm.Disconnect() 415 return nil, fmt.Errorf("PostBond error: %v", err) 416 } 417 418 if err := tc.Auth(peerID); err != nil { 419 cm.Disconnect() 420 return nil, fmt.Errorf("auth error: %v", err) 421 } 422 423 return &connectedClient{ 424 TankaClient: tc, 425 cm: cm, 426 }, nil 427 } 428 429 func mustEncode(thing interface{}) json.RawMessage { 430 b, err := json.Marshal(thing) 431 if err != nil { 432 panic("mustEncode: " + err.Error()) 433 } 434 return b 435 }