diff --git a/rmb-sdk-go/README.md b/rmb-sdk-go/README.md index f98c37c..9cff723 100644 --- a/rmb-sdk-go/README.md +++ b/rmb-sdk-go/README.md @@ -21,35 +21,8 @@ This connection could be established using a `direct-client`, or an `rmb-peer`. A process could connect to an `rmb-relay` using a direct client.\ To create a new direct client instance, a process needs to have: -- A valid mnemonics, with an activated account on the TFChain. +- A valid private key, with an activated account on the Registrar. - The key type of these mnemonics. - A relay URL that the direct client will connect to. - A session id. This could be anything, but a twin must only have a unique session id per connection. - A substrate connection. - -#### **Example** - -Creating a new direct client instance: - -```Go -subManager := substrate.NewManager("wss://tfchain.dev.grid.tf/ws") -sub, err := subManager.Substrate() -if err != nil { - return fmt.Errorf("failed to connect to substrate: %w", err) -} - -defer sub.Close() -client, err := direct.NewRpcClient(direct.KeyTypeSr25519, mnemonics, "wss://relay.dev.grid.tf", "test-client", sub, false) -if err != nil { - return fmt.Errorf("failed to create direct client: %w", err) -} -``` - -Assuming there is a remote calculator process that could add two integers, an rmb call using the direct client would look like this: - -```Go -x := 1 -y := 2 -var sum int -err := client.Call(ctx, destinationTwinID, "calculator.add", []int{x, y}, &sum) -``` diff --git a/rmb-sdk-go/examples/rpc_client/README.md b/rmb-sdk-go/examples/rpc_client/README.md index 3c14c25..e4253aa 100644 --- a/rmb-sdk-go/examples/rpc_client/README.md +++ b/rmb-sdk-go/examples/rpc_client/README.md @@ -6,7 +6,7 @@ This is a `Go` example for the `RMB` [rpc client](https://github.com/threefoldte To use the example, you needs to: -- Set the mnemonics variable to a valid mnemonics, with an activated account on the TFChain. +- Set the private key variable to a valid private key, with an activated account on the Registrar. - A node id to send the call to ## Usage diff --git a/rmb-sdk-go/examples/rpc_client/main.go b/rmb-sdk-go/examples/rpc_client/main.go index c9b97cd..301e35a 100644 --- a/rmb-sdk-go/examples/rpc_client/main.go +++ b/rmb-sdk-go/examples/rpc_client/main.go @@ -2,11 +2,11 @@ package main import ( "context" + "encoding/hex" "fmt" "log" "time" - substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go/peer" ) @@ -16,13 +16,20 @@ type version struct { } func app() error { - mnemonics := "" - subNodeURL := "wss://tfchain.dev.grid.tf/ws" - relayURL := "wss://relay.dev.grid.tf" + privateKey := "" - subManager := substrate.NewManager(subNodeURL) + privateKeyBytes, err := hex.DecodeString(privateKey) + if err != nil { + return fmt.Errorf("failed to decode private key: %w", err) + } - client, err := peer.NewRpcClient(context.Background(), mnemonics, subManager, peer.WithRelay(relayURL), peer.WithSession("test-client")) + client, err := peer.NewRpcClient( + context.Background(), + privateKeyBytes, + peer.WithRegistrarUrl("https://registrar.dev4.grid.tf"), + peer.WithRelay("wss://relay.dev.grid.tf"), + peer.WithSession("test-client"), + ) if err != nil { return fmt.Errorf("failed to create direct client: %w", err) } diff --git a/rmb-sdk-go/peer/examples/peer/README.md b/rmb-sdk-go/peer/examples/peer/README.md index eb707d0..ac8df60 100644 --- a/rmb-sdk-go/peer/examples/peer/README.md +++ b/rmb-sdk-go/peer/examples/peer/README.md @@ -6,7 +6,7 @@ This is a `Go` example for the `RMB` [direct client](https://github.com/threefol To use the example, you needs to: -- Set the mnemonics variable to a valid mnemonics, with an activated account on the TFChain. +- Set the mnemonics variable to a valid mnemonics, with an activated account on the Registrar. - Set dst to the twinId of a remote calculator process that could add two integers ## Usage diff --git a/rmb-sdk-go/peer/examples/peer/main.go b/rmb-sdk-go/peer/examples/peer/main.go index e551ef8..2493a00 100644 --- a/rmb-sdk-go/peer/examples/peer/main.go +++ b/rmb-sdk-go/peer/examples/peer/main.go @@ -2,12 +2,12 @@ package main import ( "context" + "encoding/hex" "fmt" "math/rand" "github.com/google/uuid" "github.com/rs/zerolog/log" - substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go/peer" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go/peer/types" ) @@ -15,18 +15,23 @@ import ( var resultsChan = make(chan bool) func app() error { - mnemonics := "" - subManager := substrate.NewManager("wss://tfchain.dev.grid.tf/ws") + privateKey := "" ctx := context.Background() + privateKeyBytes, err := hex.DecodeString(privateKey) + if err != nil { + return fmt.Errorf("failed to decode private key: %w", err) + } + peer, err := peer.NewPeer( ctx, - mnemonics, - subManager, + privateKeyBytes, relayCallback, + peer.WithRegistrarUrl("https://registrar.dev4.grid.tf"), peer.WithRelay("wss://relay.dev.grid.tf"), peer.WithSession("test-client"), peer.WithInMemoryExpiration(6*60*60), // six hours + peer.WithKeyType(peer.KeyTypeEd25519), ) if err != nil { diff --git a/rmb-sdk-go/peer/examples/peer_pingmany/main.go b/rmb-sdk-go/peer/examples/peer_pingmany/main.go index c2290c2..b565f8c 100644 --- a/rmb-sdk-go/peer/examples/peer_pingmany/main.go +++ b/rmb-sdk-go/peer/examples/peer_pingmany/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/hex" "encoding/json" "fmt" "net/http" @@ -10,17 +11,14 @@ import ( "time" "github.com/rs/zerolog/log" - substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go/peer" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go/peer/types" - // "rmbClient/peer" ) const ( - chainUrl = "wss://tfchain.grid.tf/" - relayUrl = "ws://localhost:" - mnemonic = "" + relayUrl = "ws://localhost:" + privateKey = "" ) type Node struct { @@ -32,8 +30,6 @@ var static = []uint32{7, 9, 10, 13, 14, 16, 22, 23, 24, 27, 29, 35, 46, 47, 69, const use_static = true func main() { - subMan := substrate.NewManager(chainUrl) - count := 500 var wg sync.WaitGroup wg.Add(count) @@ -59,10 +55,16 @@ func main() { log.Info().Uint32("twin", env.Source.Twin).Str("version", version).Msg("received response") } + privateKeyBytes, err := hex.DecodeString(privateKey) + if err != nil { + log.Error().Err(err).Msg("failed to decode private key") + return + } + bus, err := peer.NewPeer(context.Background(), - mnemonic, - subMan, + privateKeyBytes, handler, + peer.WithRegistrarUrl("https://registrar.dev4.grid.tf"), peer.WithKeyType(peer.KeyTypeSr25519), peer.WithSession("rmb-playground999"), peer.WithInMemoryExpiration(10*60*60), // in seconds that's 10 hours diff --git a/rmb-sdk-go/peer/examples/router_server/README.md b/rmb-sdk-go/peer/examples/router_server/README.md index d7c10e4..c52b406 100644 --- a/rmb-sdk-go/peer/examples/router_server/README.md +++ b/rmb-sdk-go/peer/examples/router_server/README.md @@ -6,7 +6,7 @@ This is a `Go` example for the `RMB` [peer router using direct client](https://g To use the example, you needs to: -- Set the mnemonics variable to a valid mnemonics for client peer and server, with an activated account on the TFChain. +- Set the mnemonics variable to a valid mnemonics for client peer and server, with an activated account on the Registrar. - set the client peer destination twin and session with the ones of the created peer router. - make sure you are running the server before the client peer. diff --git a/rmb-sdk-go/peer/examples/router_server/main.go b/rmb-sdk-go/peer/examples/router_server/main.go index 1677735..12a7bb5 100644 --- a/rmb-sdk-go/peer/examples/router_server/main.go +++ b/rmb-sdk-go/peer/examples/router_server/main.go @@ -2,12 +2,12 @@ package main import ( "context" + "encoding/hex" "encoding/json" "errors" "fmt" "os" - substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go/peer" ) @@ -59,20 +59,25 @@ func app() error { }) // adding a peer for the router - mnemonics := "" - subManager := substrate.NewManager("wss://tfchain.dev.grid.tf/ws") + privateKey := "" ctx := context.Background() + privateKeyBytes, err := hex.DecodeString(privateKey) + if err != nil { + return fmt.Errorf("failed to decode private key: %w", err) + } + // this peer will be a 'calculator' session. // means other peers on the network need to know that // session id to use when they are making calls - _, err := peer.NewPeer( + _, err = peer.NewPeer( ctx, - mnemonics, - subManager, + privateKeyBytes, router.Serve, + peer.WithRegistrarUrl("https://registrar.dev4.grid.tf"), peer.WithRelay("wss://relay.dev.grid.tf"), peer.WithSession("calculator"), + peer.WithKeyType(peer.KeyTypeEd25519), ) if err != nil { diff --git a/rmb-sdk-go/peer/examples/rpc/README.md b/rmb-sdk-go/peer/examples/rpc/README.md index a65b0f8..79ced1f 100644 --- a/rmb-sdk-go/peer/examples/rpc/README.md +++ b/rmb-sdk-go/peer/examples/rpc/README.md @@ -6,7 +6,7 @@ This is a `Go` example for the `RMB` [rpc client](https://github.com/threefoldte To use the example, you needs to: -- Set the mnemonics variable to a valid mnemonics, with an activated account on the TFChain. +- Set the mnemonics variable to a valid mnemonics, with an activated account on the Registrar. - A twinId of a remote calculator process that could add two integers ## Usage diff --git a/rmb-sdk-go/peer/examples/rpc/main.go b/rmb-sdk-go/peer/examples/rpc/main.go index 73c768f..5222fce 100644 --- a/rmb-sdk-go/peer/examples/rpc/main.go +++ b/rmb-sdk-go/peer/examples/rpc/main.go @@ -2,23 +2,27 @@ package main import ( "context" + "encoding/hex" "fmt" "time" "log" - substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go/peer" ) func app() error { - mnemonics := "" - subManager := substrate.NewManager("wss://tfchain.dev.grid.tf/ws") + privateKey := "" + + privateKeyBytes, err := hex.DecodeString(privateKey) + if err != nil { + return fmt.Errorf("failed to decode private key: %w", err) + } client, err := peer.NewRpcClient( context.Background(), - mnemonics, - subManager, + privateKeyBytes, + peer.WithRegistrarUrl("https://registrar.dev4.grid.tf"), peer.WithKeyType(peer.KeyTypeSr25519), peer.WithRelay("wss://relay.dev.grid.tf"), peer.WithSession("test-client"), diff --git a/rmb-sdk-go/peer/peer.go b/rmb-sdk-go/peer/peer.go index dc05f12..c83714b 100644 --- a/rmb-sdk-go/peer/peer.go +++ b/rmb-sdk-go/peer/peer.go @@ -35,10 +35,11 @@ const ( // messages. An error can be non-nil error if verification or decryption failed type Handler func(ctx context.Context, peer *Peer, env *types.Envelope, err error) -type cacheFactory = func(inner TwinDB, chainURL string) (TwinDB, error) +type cacheFactory = func(inner TwinDB) (TwinDB, error) type peerCfg struct { relayURLs []string + registrarUrl string keyType string session string enableEncryption bool @@ -69,6 +70,13 @@ func WithRelay(urls ...string) PeerOpt { } } +// WithRelay set up the relay url, default is mainnet relay +func WithRegistrarUrl(url string) PeerOpt { + return func(p *peerCfg) { + p.registrarUrl = url + } +} + // WithKeyType set up the mnemonic key type, default is Sr25519 func WithKeyType(keyType string) PeerOpt { return func(p *peerCfg) { @@ -91,8 +99,8 @@ func WithEncoder(encoder encoder.Encoder) PeerOpt { // if ttl == 0, twins are cached forever func WithTmpCacheExpiration(ttl uint64) PeerOpt { return func(pc *peerCfg) { - pc.cacheFactory = func(inner TwinDB, chainURL string) (TwinDB, error) { - return newTmpCache(ttl, inner, chainURL) + pc.cacheFactory = func(inner TwinDB) (TwinDB, error) { + return newTmpCache(ttl, inner) } } } @@ -100,7 +108,7 @@ func WithTmpCacheExpiration(ttl uint64) PeerOpt { // if ttl == 0 twins are cached forever func WithInMemoryExpiration(ttl uint64) PeerOpt { return func(pc *peerCfg) { - pc.cacheFactory = func(inner TwinDB, chainURL string) (TwinDB, error) { + pc.cacheFactory = func(inner TwinDB) (TwinDB, error) { return newInMemoryCache(inner, ttl), nil } } @@ -129,17 +137,17 @@ func generateSecureKey(identity substrate.Identity) (*secp256k1.PrivateKey, erro return priv, nil } -func getIdentity(keytype string, mnemonics string) (substrate.Identity, error) { +func getIdentity(keyType string, privateKey []byte) (substrate.Identity, error) { var identity substrate.Identity var err error - switch keytype { + switch keyType { case KeyTypeEd25519: - identity, err = substrate.NewIdentityFromEd25519Phrase(mnemonics) + identity, err = substrate.NewIdentityFromEd25519Key(privateKey) case KeyTypeSr25519: - identity, err = substrate.NewIdentityFromSr25519Phrase(mnemonics) + // identity, err = substrate.NewIdentityFromSr25519Phrase(privateKeyBytes) //TODO: default: - return nil, fmt.Errorf("invalid key type %s, should be one of %s or %s ", keytype, KeyTypeEd25519, KeyTypeSr25519) + return nil, fmt.Errorf("invalid key type %s, should be one of %s or %s ", keyType, KeyTypeEd25519, KeyTypeSr25519) } if err != nil { @@ -156,17 +164,17 @@ func getIdentity(keytype string, mnemonics string) (substrate.Identity, error) { // Call() will panic if called while the directClient's context is canceled. func NewPeer( ctx context.Context, - mnemonics string, - subManager substrate.Manager, + privateKey []byte, handler Handler, opts ...PeerOpt, ) (*Peer, error) { cfg := &peerCfg{ relayURLs: []string{"wss://relay.grid.tf"}, + registrarUrl: "https://registrar.prod4.grid.tf", session: "", enableEncryption: true, keyType: KeyTypeSr25519, - cacheFactory: func(inner TwinDB, _ string) (TwinDB, error) { + cacheFactory: func(inner TwinDB) (TwinDB, error) { return newInMemoryCache(inner, 0), nil }, } @@ -178,22 +186,12 @@ func NewPeer( if cfg.encoder == nil { cfg.encoder = encoder.NewJSONEncoder() } - identity, err := getIdentity(cfg.keyType, mnemonics) - if err != nil { - return nil, err - } - - subConn, err := subManager.Substrate() + identity, err := getIdentity(cfg.keyType, privateKey) if err != nil { return nil, err } - api, _, err := subConn.GetClient() - if err != nil { - return nil, err - } - - twinDB, err := cfg.cacheFactory(NewTwinDB(subConn), api.Client.URL()) + twinDB, err := cfg.cacheFactory(NewTwinDB(cfg.registrarUrl)) if err != nil { return nil, err } @@ -233,11 +231,9 @@ func NewPeer( sort.Slice(relayURLs, func(i, j int) bool { return strings.ToLower(relayURLs[i]) < strings.ToLower(relayURLs[j]) }) relayURLs = slices.Compact(relayURLs) - joinURLs := strings.Join(relayURLs, "_") - - if !bytes.Equal(twin.E2EKey, publicKey) || twin.Relay == nil || joinURLs != *twin.Relay { - log.Info().Str("Relay url/s", joinURLs).Msg("twin relay/public key didn't match, updating on chain ...") - if _, err = subConn.UpdateTwin(identity, joinURLs, publicKey); err != nil { + if !bytes.Equal(twin.E2EKey, publicKey) || twin.Relay == nil || relayURLs[0] != *twin.Relay { // TODO: multiple relays (slice?) + log.Info().Strs("Relay url/s", relayURLs).Msg("twin relay/public key didn't match, updating on registrar ...") + if err = UpdateTwin(twin.ID, privateKey, publicKey, relayURLs, cfg.registrarUrl); err != nil { return nil, errors.Wrap(err, "could not update twin relay information") } } diff --git a/rmb-sdk-go/peer/rpc.go b/rmb-sdk-go/peer/rpc.go index 206d6c4..1288354 100644 --- a/rmb-sdk-go/peer/rpc.go +++ b/rmb-sdk-go/peer/rpc.go @@ -8,7 +8,6 @@ import ( "sync" "github.com/google/uuid" - substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go" "github.com/threefoldtech/tfgrid-sdk-go/rmb-sdk-go/peer/types" ) @@ -34,8 +33,7 @@ type RpcClient struct { // it easy to make rpc calls func NewRpcClient( ctx context.Context, - mnemonics string, - subManager substrate.Manager, + privateKey []byte, opts ...PeerOpt) (*RpcClient, error) { rpc := RpcClient{ @@ -44,8 +42,7 @@ func NewRpcClient( base, err := NewPeer( ctx, - mnemonics, - subManager, + privateKey, rpc.router, opts..., ) diff --git a/rmb-sdk-go/peer/twindb.go b/rmb-sdk-go/peer/twindb.go index 7438ca4..4cef7bd 100644 --- a/rmb-sdk-go/peer/twindb.go +++ b/rmb-sdk-go/peer/twindb.go @@ -1,17 +1,19 @@ package peer import ( + "crypto/ed25519" + "encoding/base64" "encoding/json" "fmt" - "net/url" + "net/http" "os" "path/filepath" + "strings" "sync" "time" "github.com/pkg/errors" "github.com/rs/zerolog/log" - substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go" ) var ( @@ -28,48 +30,155 @@ type TwinDB interface { type Twin struct { ID uint32 PublicKey []byte - Relay *string + Relay *string // TODO: multiple relays (slice?) E2EKey []byte Timestamp uint64 } +type RegistrarTwin struct { + TwinID uint64 `json:"twin_id"` + Relays []string `json:"relays"` + RMBEncKey string `json:"rmb_enc_key"` + PublicKey string `json:"public_key"` +} + type twinDB struct { - subConn *substrate.Substrate + httpClient *http.Client + registrarUrl string } // NewTwinDB creates a new twinDBImpl instance, with a non expiring cache. -func NewTwinDB(subConn *substrate.Substrate) TwinDB { +func NewTwinDB(registrarUrl string) TwinDB { return &twinDB{ - subConn: subConn, + httpClient: &http.Client{}, + registrarUrl: registrarUrl, } } +type updateTwin struct { + Relays []string `json:"relays"` + RMBEncKey string `json:"rmb_enc_key"` +} + // GetTwin gets Twin from cache if present. if not, gets it from substrate client and caches it. func (t *twinDB) Get(id uint32) (Twin, error) { - substrateTwin, err := t.subConn.GetTwin(id) + req, _ := http.NewRequest( + "GET", + fmt.Sprintf("%s/v1/accounts?twin_id=%v", t.registrarUrl, id), + nil, + ) + + resp, err := t.httpClient.Do(req) if err != nil { return Twin{}, errors.Wrapf(err, "could not get twin with id %d", id) } + defer resp.Body.Close() + + var registrarTwin RegistrarTwin + if err = json.NewDecoder(resp.Body).Decode(®istrarTwin); err != nil { + return Twin{}, err + } var relay *string - if substrateTwin.Relay.HasValue { - relay = &substrateTwin.Relay.AsValue + if len(registrarTwin.Relays) > 0 { + relay = ®istrarTwin.Relays[0] // TODO: will relays be a slice??? + } + + pk, err := base64.StdEncoding.DecodeString(registrarTwin.PublicKey) + if err != nil { + return Twin{}, err + } + + e2ePK, err := base64.StdEncoding.DecodeString(registrarTwin.RMBEncKey) + if err != nil { + return Twin{}, err + } + + if len(e2ePK) == 0 { + e2ePK = pk } - _, PK := substrateTwin.Pk.Unwrap() twin := Twin{ ID: id, - PublicKey: substrateTwin.Account.PublicKey(), + PublicKey: pk, Relay: relay, - E2EKey: PK, + E2EKey: e2ePK, } return twin, nil } func (t *twinDB) GetByPk(pk []byte) (uint32, error) { - return t.subConn.GetTwinByPubKey(pk) + req, _ := http.NewRequest( + "GET", + fmt.Sprintf("%s/v1/accounts?public_key=%v", t.registrarUrl, base64.StdEncoding.EncodeToString(pk)), + nil, + ) + + resp, err := t.httpClient.Do(req) + if err != nil { + return 0, errors.Wrap(err, "could not get twin") + } + defer resp.Body.Close() + + var registrarTwin RegistrarTwin + if err = json.NewDecoder(resp.Body).Decode(®istrarTwin); err != nil { + return 0, err + } + + return uint32(registrarTwin.TwinID), nil +} + +func UpdateTwin(twinID uint32, privateKey, rmbEncKey []byte, relays []string, registrarUrl string) error { + client := &http.Client{} + + timestamp := time.Now().Unix() + challenge := []byte(fmt.Sprintf("%d:%v", timestamp, twinID)) + signature := ed25519.Sign(privateKey, challenge) + + fmt.Printf("rmbEncKey: %v\n", rmbEncKey) + updates := updateTwin{ + Relays: relays, + RMBEncKey: base64.StdEncoding.EncodeToString(rmbEncKey), + } + + jsonData, err := json.Marshal(updates) + if err != nil { + return err + } + + req, _ := http.NewRequest( + "PATCH", + fmt.Sprintf("%s/v1/accounts/%v", registrarUrl, twinID), + strings.NewReader(string(jsonData)), + ) + + authHeader := fmt.Sprintf( + "%s:%s", + base64.StdEncoding.EncodeToString(challenge), + base64.StdEncoding.EncodeToString(signature), + ) + + req.Header.Set("X-Auth", authHeader) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + return err + } + + if resp == nil { + return errors.New("failed to update twin, no response received") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("request failed with %d", resp.StatusCode) + } + + return nil } // if ttl == 0, then the data will stay forever @@ -126,12 +235,8 @@ type tmpCache struct { inner TwinDB } -func newTmpCache(ttl uint64, inner TwinDB, chainURL string) (TwinDB, error) { - u, err := url.Parse(chainURL) - if err != nil { - return nil, err - } - path := filepath.Join(os.TempDir(), "rmb-cache", u.Host) +func newTmpCache(ttl uint64, inner TwinDB) (TwinDB, error) { + path := filepath.Join(os.TempDir(), "rmb-cache") if err := os.MkdirAll(path, 0755); err != nil { return nil, err } diff --git a/rmb-sdk-go/wiki/using_rmb_peer.md b/rmb-sdk-go/wiki/using_rmb_peer.md index 9ec374e..1e5e65c 100644 --- a/rmb-sdk-go/wiki/using_rmb_peer.md +++ b/rmb-sdk-go/wiki/using_rmb_peer.md @@ -13,7 +13,7 @@ rmb-peer -m "" ``` > Can be added to the system service with systemd so it can be running all the time.\ -> run `rmb-peer -h` to customize the peer, including which relay and which tfchain to connect to. +> run `rmb-peer -h` to customize the peer, including which relay and which registrar to connect to. ## Example