diff --git a/cmd/gossamer/config.go b/cmd/gossamer/config.go index 3f0abca651..9907abdaa8 100644 --- a/cmd/gossamer/config.go +++ b/cmd/gossamer/config.go @@ -407,6 +407,7 @@ func setDotGlobalConfig(ctx *cli.Context, tomlCfg *ctoml.Config, cfg *dot.Global cfg.MetricsPort = tomlCfg.Global.MetricsPort } + // TODO: generate random name if one is not assigned (see issue #1496) // check --name flag and update node configuration if name := ctx.GlobalString(NameFlag.Name); name != "" { cfg.Name = name @@ -442,6 +443,8 @@ func setDotGlobalConfig(ctx *cli.Context, tomlCfg *ctoml.Config, cfg *dot.Global cfg.MetricsPort = uint32(metricsPort) } + cfg.NoTelemetry = ctx.Bool("no-telemetry") + logger.Debug( "global configuration", "name", cfg.Name, diff --git a/cmd/gossamer/config_test.go b/cmd/gossamer/config_test.go index f9fa04c0d4..c0485bc8a5 100644 --- a/cmd/gossamer/config_test.go +++ b/cmd/gossamer/config_test.go @@ -222,6 +222,20 @@ func TestGlobalConfigFromFlags(t *testing.T) { MetricsPort: uint32(9871), }, }, + { + "Test gossamer --no-telemetry", + []string{"config", "no-telemetry", "name"}, + []interface{}{testCfgFile.Name(), true, testCfg.Global.Name}, + dot.GlobalConfig{ + Name: testCfg.Global.Name, + ID: testCfg.Global.ID, + BasePath: testCfg.Global.BasePath, + LogLvl: log.LvlInfo, + PublishMetrics: testCfg.Global.PublishMetrics, + MetricsPort: testCfg.Global.MetricsPort, + NoTelemetry: true, + }, + }, } for _, c := range testcases { diff --git a/cmd/gossamer/flags.go b/cmd/gossamer/flags.go index 0a877af8a5..8708433a73 100644 --- a/cmd/gossamer/flags.go +++ b/cmd/gossamer/flags.go @@ -98,6 +98,12 @@ var ( Name: "metrics-port", Usage: "Set metric listening port ", } + + // NoTelemetryFlag stops publishing telemetry to default defined in genesis.json + NoTelemetryFlag = cli.BoolFlag{ + Name: "no-telemetry", + Usage: "Disable connecting to the Substrate telemetry server", + } ) // Initialization-only flags @@ -295,6 +301,9 @@ var ( // metrics flag PublishMetricsFlag, MetricsPortFlag, + + // telemetry flags + NoTelemetryFlag, } ) diff --git a/dot/config.go b/dot/config.go index 73d17e0880..57d1e14634 100644 --- a/dot/config.go +++ b/dot/config.go @@ -50,6 +50,7 @@ type GlobalConfig struct { LogLvl log.Lvl PublishMetrics bool MetricsPort uint32 + NoTelemetry bool } // LogConfig represents the log levels for individual packages diff --git a/dot/node.go b/dot/node.go index b4de844251..cc11ef1330 100644 --- a/dot/node.go +++ b/dot/node.go @@ -23,21 +23,23 @@ import ( "os/signal" "path" "runtime/debug" + "strconv" "sync" "syscall" + "time" + "github.com/ChainSafe/chaindb" gssmrmetrics "github.com/ChainSafe/gossamer/dot/metrics" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/telemetry" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/services" + log "github.com/ChainSafe/log15" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/prometheus" - - "github.com/ChainSafe/chaindb" - log "github.com/ChainSafe/log15" ) var logger = log.New("pkg", "dot") @@ -306,6 +308,28 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node, publishMetrics(cfg) } + gd, err := stateSrvc.Storage.GetGenesisData() + if err != nil { + return nil, err + } + + if cfg.Global.NoTelemetry { + return node, nil + } + + telemetry.GetInstance().AddConnections(gd.TelemetryEndpoints) + data := &telemetry.ConnectionData{ + Authority: cfg.Core.GrandpaAuthority, + Chain: sysSrvc.ChainName(), + GenesisHash: stateSrvc.Block.GenesisHash().String(), + SystemName: sysSrvc.SystemName(), + NodeName: cfg.Global.Name, + SystemVersion: sysSrvc.SystemVersion(), + NetworkID: networkSrvc.NetworkState().PeerID, + StartTime: strconv.FormatInt(time.Now().UnixNano(), 10), + } + telemetry.GetInstance().SendConnection(data) + return node, nil } diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go index 91f531df2e..28eea721d5 100644 --- a/dot/rpc/modules/api.go +++ b/dot/rpc/modules/api.go @@ -90,7 +90,7 @@ type RuntimeAPI interface { type SystemAPI interface { SystemName() string SystemVersion() string - NodeName() string Properties() map[string]interface{} ChainType() string + ChainName() string } diff --git a/dot/rpc/modules/system.go b/dot/rpc/modules/system.go index 4123a470d3..7831bea61e 100644 --- a/dot/rpc/modules/system.go +++ b/dot/rpc/modules/system.go @@ -83,7 +83,7 @@ func NewSystemModule(net NetworkAPI, sys SystemAPI, core CoreAPI, // Chain returns the runtime chain func (sm *SystemModule) Chain(r *http.Request, req *EmptyRequest, res *string) error { - *res = sm.systemAPI.NodeName() + *res = sm.systemAPI.ChainName() return nil } @@ -161,7 +161,7 @@ func (sm *SystemModule) NodeRoles(r *http.Request, req *EmptyRequest, res *[]int // AccountNextIndex Returns the next valid index (aka. nonce) for given account. func (sm *SystemModule) AccountNextIndex(r *http.Request, req *StringRequest, res *U64Response) error { if req == nil || len(req.String) == 0 { - return errors.New("Account address must be valid") + return errors.New("account address must be valid") } addressPubKey := crypto.PublicAddressToByteArray(common.Address(req.String)) diff --git a/dot/rpc/modules/system_test.go b/dot/rpc/modules/system_test.go index c1f982530c..98c52935a1 100644 --- a/dot/rpc/modules/system_test.go +++ b/dot/rpc/modules/system_test.go @@ -210,10 +210,9 @@ func (api *mockSystemAPI) SystemVersion() string { return api.info.SystemVersion } -func (api *mockSystemAPI) NodeName() string { +func (api *mockSystemAPI) ChainName() string { return api.genData.Name } - func (api *mockSystemAPI) Properties() map[string]interface{} { return nil } diff --git a/dot/sync/syncer.go b/dot/sync/syncer.go index b04aadb864..dcae7dc8ae 100644 --- a/dot/sync/syncer.go +++ b/dot/sync/syncer.go @@ -24,12 +24,12 @@ import ( "os" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/telemetry" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/blocktree" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/runtime" rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" - log "github.com/ChainSafe/log15" ) @@ -339,6 +339,7 @@ func (s *Service) handleBlock(block *types.Block) error { } } else { logger.Debug("🔗 imported block", "number", block.Header.Number, "hash", block.Header.Hash()) + telemetry.GetInstance().SendBlockImport(block.Header.Hash().String(), block.Header.Number) } // handle consensus digest for authority changes diff --git a/dot/system/service.go b/dot/system/service.go index 9d08f7fd48..8ce3b647f7 100644 --- a/dot/system/service.go +++ b/dot/system/service.go @@ -50,8 +50,8 @@ func (s *Service) SystemVersion() string { return s.systemInfo.SystemVersion } -// NodeName returns the nodeName (chain name) -func (s *Service) NodeName() string { +// ChainName returns the chain name defined in genesis.json +func (s *Service) ChainName() string { return s.genesisData.Name } diff --git a/dot/system/service_test.go b/dot/system/service_test.go index 5e49d6cfed..cd0aa0f9cc 100644 --- a/dot/system/service_test.go +++ b/dot/system/service_test.go @@ -24,10 +24,10 @@ import ( "github.com/stretchr/testify/require" ) -func TestService_NodeName(t *testing.T) { +func TestService_ChainName(t *testing.T) { svc := newTestService() - name := svc.NodeName() + name := svc.ChainName() require.Equal(t, "gssmr", name) } diff --git a/dot/telemetry/telemetry.go b/dot/telemetry/telemetry.go new file mode 100644 index 0000000000..4577a4a1f3 --- /dev/null +++ b/dot/telemetry/telemetry.go @@ -0,0 +1,123 @@ +// Copyright 2021 ChainSafe Systems (ON) Corp. +// This file is part of gossamer. +// +// The gossamer library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The gossamer library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the gossamer library. If not, see . +package telemetry + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "sync" + "time" + + "github.com/ChainSafe/gossamer/lib/genesis" + "github.com/gorilla/websocket" + log "github.com/sirupsen/logrus" +) + +// Handler struct for holding telemetry related things +type Handler struct { + buf bytes.Buffer + wsConn []*websocket.Conn + telemetryLogger *log.Entry +} + +// MyJSONFormatter struct for defining JSON Formatter +type MyJSONFormatter struct { +} + +// Format function for handling JSON formatting, this overrides default logging formatter to remove +// log level, line number and timestamp +func (f *MyJSONFormatter) Format(entry *log.Entry) ([]byte, error) { + serialized, err := json.Marshal(entry.Data) + if err != nil { + return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err) + } + return append(serialized, '\n'), nil +} + +var ( + once sync.Once + handlerInstance *Handler +) + +// GetInstance singleton pattern to for accessing TelemetryHandler +func GetInstance() *Handler { + if handlerInstance == nil { + once.Do( + func() { + handlerInstance = &Handler{ + buf: bytes.Buffer{}, + } + log.SetOutput(&handlerInstance.buf) + log.SetFormatter(new(MyJSONFormatter)) + }) + } + return handlerInstance +} + +// AddConnections adds connections to telemetry sever +func (h *Handler) AddConnections(conns []*genesis.TelemetryEndpoint) { + for _, v := range conns { + c, _, err := websocket.DefaultDialer.Dial(v.Endpoint, nil) + if err != nil { + fmt.Printf("Error %v\n", err) + return + } + h.wsConn = append(h.wsConn, c) + } +} + +// ConnectionData struct to hold connection data +type ConnectionData struct { + Authority bool + Chain string + GenesisHash string + SystemName string + NodeName string + SystemVersion string + NetworkID string + StartTime string +} + +// SendConnection sends connection request message to telemetry connection +func (h *Handler) SendConnection(data *ConnectionData) { + payload := log.Fields{"authority": data.Authority, "chain": data.Chain, "config": "", "genesis_hash": data.GenesisHash, + "implementation": data.SystemName, "msg": "system.connected", "name": data.NodeName, "network_id": data.NetworkID, "startup_time": data.StartTime, + "version": data.SystemVersion} + h.telemetryLogger = log.WithFields(log.Fields{"id": 1, "payload": payload, "ts": time.Now()}) + h.telemetryLogger.Print() + h.sendTelemtry() +} + +// SendBlockImport sends block imported message to telemetry connection +func (h *Handler) SendBlockImport(bestHash string, height *big.Int) { + payload := log.Fields{"best": bestHash, "height": height.Int64(), "msg": "block.import", "origin": "NetworkInitialSync"} + h.telemetryLogger = log.WithFields(log.Fields{"id": 1, "payload": payload, "ts": time.Now()}) + h.telemetryLogger.Print() + h.sendTelemtry() +} + +func (h *Handler) sendTelemtry() { + for _, c := range h.wsConn { + err := c.WriteMessage(websocket.TextMessage, h.buf.Bytes()) + if err != nil { + // TODO (ed) determine how to handle this error + fmt.Printf("ERROR connecting to telemetry %v\n", err) + } + } + h.buf.Reset() +} diff --git a/dot/telemetry/telemetry_test.go b/dot/telemetry/telemetry_test.go new file mode 100644 index 0000000000..d8a505f6cc --- /dev/null +++ b/dot/telemetry/telemetry_test.go @@ -0,0 +1,78 @@ +package telemetry + +import ( + "log" + "math/big" + "net/http" + "os" + "testing" + "time" + + "github.com/ChainSafe/gossamer/lib/genesis" + "github.com/gorilla/websocket" + "github.com/stretchr/testify/require" +) + +var upgrader = websocket.Upgrader{} +var lastMessage []byte + +func TestMain(m *testing.M) { + // start server to listen for websocket connections + upgrader.CheckOrigin = func(r *http.Request) bool { return true } + http.HandleFunc("/", listen) + go http.ListenAndServe("127.0.0.1:8001", nil) + + time.Sleep(time.Millisecond) + // instantiate telemetry to connect to websocket (test) server + var testEndpoints []*genesis.TelemetryEndpoint + var testEndpoint1 = &genesis.TelemetryEndpoint{ + Endpoint: "ws://127.0.0.1:8001/", + Verbosity: 0, + } + GetInstance().AddConnections(append(testEndpoints, testEndpoint1)) + + // Start all tests + code := m.Run() + os.Exit(code) +} +func TestHandler_SendConnection(t *testing.T) { + expected := []byte(`{"id":1,"payload":{"authority":false,"chain":"chain","config":"","genesis_hash":"hash","implementation":"systemName","msg":"system.connected","name":"nodeName","network_id":"netID","startup_time":"startTime","version":"version"},"ts":`) + data := &ConnectionData{ + Authority: false, + Chain: "chain", + GenesisHash: "hash", + SystemName: "systemName", + NodeName: "nodeName", + SystemVersion: "version", + NetworkID: "netID", + StartTime: "startTime", + } + GetInstance().SendConnection(data) + time.Sleep(time.Millisecond) + // note, we only check the first 234 bytes because the remaining bytes are the timestamp, which we can't estimate + require.Equal(t, expected, lastMessage[:234]) +} + +func TestHandler_SendBlockImport(t *testing.T) { + expected := []byte(`{"id":1,"payload":{"best":"hash","height":2,"msg":"block.import","origin":"NetworkInitialSync"},"ts":`) + GetInstance().SendBlockImport("hash", big.NewInt(2)) + time.Sleep(time.Millisecond) + // note, we only check the first 101 bytes because the remaining bytes are the timestamp, which we can't estimate + require.Equal(t, expected, lastMessage[:101]) +} + +func listen(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Printf("Error %v\n", err) + } + defer c.Close() + for { + _, msg, err := c.ReadMessage() + if err != nil { + log.Printf("read err %v", err) + break + } + lastMessage = msg + } +} diff --git a/go.mod b/go.mod index 9cd50dc071..a38499f774 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/perlin-network/life v0.0.0-20191203030451-05c0e0f7eaea github.com/rs/cors v1.7.0 // indirect + github.com/sirupsen/logrus v1.2.0 github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca // indirect github.com/urfave/cli v1.20.0 @@ -56,7 +57,7 @@ require ( golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect - golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd // indirect + golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd google.golang.org/appengine v1.6.5 // indirect google.golang.org/protobuf v1.25.0 ) diff --git a/go.sum b/go.sum index 4f336bffd9..d838e5b9da 100644 --- a/go.sum +++ b/go.sum @@ -264,6 +264,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= @@ -601,6 +602,7 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= diff --git a/lib/genesis/genesis.go b/lib/genesis/genesis.go index 458dc9e9e0..5617243d05 100644 --- a/lib/genesis/genesis.go +++ b/lib/genesis/genesis.go @@ -22,29 +22,37 @@ import ( // Genesis stores the data parsed from the genesis configuration file type Genesis struct { - Name string `json:"name"` - ID string `json:"id"` - ChainType string `json:"chainType"` - Bootnodes []string `json:"bootNodes"` - ProtocolID string `json:"protocolId"` - Genesis Fields `json:"genesis"` - Properties map[string]interface{} `json:"properties"` - ForkBlocks []string `json:"forkBlocks"` - BadBlocks []string `json:"badBlocks"` - ConsensusEngine string `json:"consensusEngine"` + Name string `json:"name"` + ID string `json:"id"` + ChainType string `json:"chainType"` + Bootnodes []string `json:"bootNodes"` + TelemetryEndpoints []interface{} `json:"telemetryEndpoints"` + ProtocolID string `json:"protocolId"` + Genesis Fields `json:"genesis"` + Properties map[string]interface{} `json:"properties"` + ForkBlocks []string `json:"forkBlocks"` + BadBlocks []string `json:"badBlocks"` + ConsensusEngine string `json:"consensusEngine"` } // Data defines the genesis file data formatted for trie storage type Data struct { - Name string - ID string - ChainType string - Bootnodes [][]byte - ProtocolID string - Properties map[string]interface{} - ForkBlocks []string - BadBlocks []string - ConsensusEngine string + Name string + ID string + ChainType string + Bootnodes [][]byte + TelemetryEndpoints []*TelemetryEndpoint + ProtocolID string + Properties map[string]interface{} + ForkBlocks []string + BadBlocks []string + ConsensusEngine string +} + +// TelemetryEndpoint struct to hold telemetry endpoint information +type TelemetryEndpoint struct { + Endpoint string + Verbosity int } // Fields stores genesis raw data, and human readable runtime data @@ -56,15 +64,16 @@ type Fields struct { // GenesisData formats genesis for trie storage func (g *Genesis) GenesisData() *Data { return &Data{ - Name: g.Name, - ID: g.ID, - ChainType: g.ChainType, - Bootnodes: common.StringArrayToBytes(g.Bootnodes), - ProtocolID: g.ProtocolID, - Properties: g.Properties, - ForkBlocks: g.ForkBlocks, - BadBlocks: g.BadBlocks, - ConsensusEngine: g.ConsensusEngine, + Name: g.Name, + ID: g.ID, + ChainType: g.ChainType, + Bootnodes: common.StringArrayToBytes(g.Bootnodes), + TelemetryEndpoints: interfaceToTelemetryEndpoint(g.TelemetryEndpoints), + ProtocolID: g.ProtocolID, + Properties: g.Properties, + ForkBlocks: g.ForkBlocks, + BadBlocks: g.BadBlocks, + ConsensusEngine: g.ConsensusEngine, } } @@ -94,3 +103,31 @@ func (g *Genesis) ToRaw() error { g.Genesis.Raw["top"] = res return nil } + +func interfaceToTelemetryEndpoint(endpoints []interface{}) []*TelemetryEndpoint { + var res []*TelemetryEndpoint + for _, v := range endpoints { + epi, ok := v.([]interface{}) + if !ok { + continue + } + if len(epi) != 2 { + continue + } + eps, ok := epi[0].(string) + if !ok { + continue + } + epv, ok := epi[1].(float64) + if !ok { + continue + } + ep := &TelemetryEndpoint{ + Endpoint: eps, + Verbosity: int(epv), + } + res = append(res, ep) + } + + return res +} diff --git a/lib/genesis/genesis_test.go b/lib/genesis/genesis_test.go new file mode 100644 index 0000000000..8da2252ca6 --- /dev/null +++ b/lib/genesis/genesis_test.go @@ -0,0 +1,65 @@ +// Copyright 2019 ChainSafe Systems (ON) Corp. +// This file is part of gossamer. +// +// The gossamer library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The gossamer library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the gossamer library. If not, see . + +package genesis + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestInterfaceToTelemetryEndpoint(t *testing.T) { + testcases := []struct { + description string + values []interface{} + expected []*TelemetryEndpoint + }{ + { + "Test with wrong interface type", + []interface{}{"string"}, + nil, + }, + { + "Test with interface field len != 2", + append(testEndpoints, []interface{}{"wss://telemetry.polkadot.io/submit/"}), + nil, + }, + { + "Test with interface field 0 wrong type", + append(testEndpoints, []interface{}{float32(0), "wss://telemetry.polkadot.io/submit/"}), + nil, + }, + { + "Test with interface field 1 wrong type", + append(testEndpoints, []interface{}{"wss://telemetry.polkadot.io/submit/", "1"}), + nil, + }, + { + "Test with correctly formed values", + append(testEndpoints, testEndpoint1), + append([]*TelemetryEndpoint{}, &TelemetryEndpoint{ + Endpoint: "wss://telemetry.polkadot.io/submit/", + Verbosity: 1, + }), + }, + } + + for _, test := range testcases { + res := interfaceToTelemetryEndpoint(test.values) + require.Equal(t, test.expected, res) + } +} diff --git a/lib/genesis/test_utils.go b/lib/genesis/test_utils.go index 39de2dbc23..0a7531ac4b 100644 --- a/lib/genesis/test_utils.go +++ b/lib/genesis/test_utils.go @@ -28,6 +28,8 @@ var testBootnodes = []string{ "/dns4/p2p.cc3-1.kusama.network/tcp/30100/p2p/QmchDJtEGiEWf7Ag58HNoTg9jSGzxkSZ23VgmF6xiLKKsZ", } +var testEndpoints = []interface{}{} +var testEndpoint1 = []interface{}{"wss://telemetry.polkadot.io/submit/", float64(1)} var testProperties = map[string]interface{}{"ss58Format": float64(0), "tokenDecimals": float64(10), "tokenSymbol": "DOT"} var testForkBlocks = []string{"fork1", "forkBlock2"} @@ -36,13 +38,14 @@ var testBadBlocks = []string{"badBlock1", "badBlock2"} // TestGenesis instance of Genesis struct for testing var TestGenesis = &Genesis{ - Name: "gossamer", - ID: "gossamer", - Bootnodes: testBootnodes, - ProtocolID: testProtocolID, - Properties: testProperties, - ForkBlocks: testForkBlocks, - BadBlocks: testBadBlocks, + Name: "gossamer", + ID: "gossamer", + Bootnodes: testBootnodes, + TelemetryEndpoints: append(testEndpoints, testEndpoint1), + ProtocolID: testProtocolID, + Properties: testProperties, + ForkBlocks: testForkBlocks, + BadBlocks: testBadBlocks, } // TestFieldsHR instance of human-readable Fields struct for testing, use with TestGenesis