diff --git a/cmd/gossamer/config.go b/cmd/gossamer/config.go index 8d1a7a1692..c3ca725f8c 100644 --- a/cmd/gossamer/config.go +++ b/cmd/gossamer/config.go @@ -21,12 +21,11 @@ import ( "strconv" "strings" + database "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot" "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/genesis" - - database "github.com/ChainSafe/chaindb" log "github.com/ChainSafe/log15" "github.com/urfave/cli" ) @@ -95,6 +94,9 @@ func createDotConfig(ctx *cli.Context) (cfg *dot.Config, err error) { setDotNetworkConfig(ctx, &cfg.Network) setDotRPCConfig(ctx, &cfg.RPC) + // set system info + setSystemInfoConfig(ctx, cfg) + return cfg, nil } @@ -112,6 +114,9 @@ func createInitConfig(ctx *cli.Context) (cfg *dot.Config, err error) { // set init configuration values setDotInitConfig(ctx, &cfg.Init) + // set system info + setSystemInfoConfig(ctx, cfg) + // ensure configuration values match genesis and overwrite with genesis updateDotConfigFromGenesisJSON(ctx, cfg) @@ -137,6 +142,9 @@ func createExportConfig(ctx *cli.Context) (cfg *dot.Config) { setDotNetworkConfig(ctx, &cfg.Network) setDotRPCConfig(ctx, &cfg.RPC) + // set system info + setSystemInfoConfig(ctx, cfg) + return cfg } @@ -332,6 +340,19 @@ func setDotRPCConfig(ctx *cli.Context, cfg *dot.RPCConfig) { ) } +func setSystemInfoConfig(ctx *cli.Context, cfg *dot.Config) { + // load system information + if ctx.App != nil { + cfg.System.SystemName = ctx.App.Name + cfg.System.SystemVersion = ctx.App.Version + } + + // TODO lookup system properties from genesis file and set here (See issue #865) + cfg.System.NodeName = cfg.Global.Name + props := make(map[string]interface{}) + cfg.System.SystemProperties = props +} + // updateDotConfigFromGenesisJSON updates the configuration based on the genesis file values func updateDotConfigFromGenesisJSON(ctx *cli.Context, cfg *dot.Config) { diff --git a/cmd/gossamer/config_test.go b/cmd/gossamer/config_test.go index d7a030098f..68ed491caa 100644 --- a/cmd/gossamer/config_test.go +++ b/cmd/gossamer/config_test.go @@ -545,6 +545,7 @@ func TestUpdateConfigFromGenesisJSON(t *testing.T) { Core: testCfg.Core, Network: testCfg.Network, RPC: testCfg.RPC, + System: testCfg.System, } cfg, err := createDotConfig(ctx) @@ -585,6 +586,7 @@ func TestUpdateConfigFromGenesisJSON_Default(t *testing.T) { Core: testCfg.Core, Network: testCfg.Network, RPC: testCfg.RPC, + System: testCfg.System, } cfg, err := createDotConfig(ctx) @@ -626,7 +628,8 @@ func TestUpdateConfigFromGenesisData(t *testing.T) { NoBootstrap: testCfg.Network.NoBootstrap, NoMDNS: testCfg.Network.NoMDNS, }, - RPC: testCfg.RPC, + RPC: testCfg.RPC, + System: testCfg.System, } cfg, err := createDotConfig(ctx) diff --git a/dot/config.go b/dot/config.go index b0d12515d0..2e5fc4e53f 100644 --- a/dot/config.go +++ b/dot/config.go @@ -24,21 +24,22 @@ import ( "reflect" "unicode" + "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/node/gssmr" "github.com/ChainSafe/gossamer/node/ksmcc" - log "github.com/ChainSafe/log15" "github.com/naoina/toml" ) // Config is a collection of configurations throughout the system type Config struct { - Global GlobalConfig `toml:"global"` - Init InitConfig `toml:"init"` - Account AccountConfig `toml:"account"` - Core CoreConfig `toml:"core"` - Network NetworkConfig `toml:"network"` - RPC RPCConfig `toml:"rpc"` + Global GlobalConfig `toml:"global"` + Init InitConfig `toml:"init"` + Account AccountConfig `toml:"account"` + Core CoreConfig `toml:"core"` + Network NetworkConfig `toml:"network"` + RPC RPCConfig `toml:"rpc"` + System types.SystemInfo `toml:"-"` } // GlobalConfig is to marshal/unmarshal toml global config vars @@ -132,6 +133,10 @@ func GssmrConfig() *Config { Modules: gssmr.DefaultRPCModules, WSPort: gssmr.DefaultRPCWSPort, }, + System: types.SystemInfo{ + NodeName: gssmr.DefaultName, + SystemProperties: make(map[string]interface{}), + }, } } @@ -167,6 +172,10 @@ func KsmccConfig() *Config { Modules: ksmcc.DefaultRPCModules, WSPort: ksmcc.DefaultRPCWSPort, }, + System: types.SystemInfo{ + NodeName: ksmcc.DefaultName, + SystemProperties: make(map[string]interface{}), + }, } } diff --git a/dot/node.go b/dot/node.go index 2b21427a8b..b75b83aeb4 100644 --- a/dot/node.go +++ b/dot/node.go @@ -235,13 +235,19 @@ func NewNode(cfg *Config, ks *keystore.Keystore) (*Node, error) { } + // System Service + + // create system service and append to node services + sysSrvc := createSystemService(&cfg.System) + nodeSrvcs = append(nodeSrvcs, sysSrvc) + // RPC Service // check if rpc service is enabled if enabled := RPCServiceEnabled(cfg); enabled { // create rpc service and append rpc service to node services - rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, rt) + rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, rt, sysSrvc) nodeSrvcs = append(nodeSrvcs, rpcSrvc) } else { diff --git a/dot/rpc/http.go b/dot/rpc/http.go index a2bd610499..27a9cfd0eb 100644 --- a/dot/rpc/http.go +++ b/dot/rpc/http.go @@ -44,6 +44,7 @@ type HTTPServerConfig struct { RuntimeAPI modules.RuntimeAPI TransactionQueueAPI modules.TransactionQueueAPI RPCAPI modules.RPCAPI + SystemAPI modules.SystemAPI Host string RPCPort uint32 WSEnabled bool @@ -81,7 +82,7 @@ func (h *HTTPServer) RegisterModules(mods []string) { var srvc interface{} switch mod { case "system": - srvc = modules.NewSystemModule(h.serverConfig.NetworkAPI) + srvc = modules.NewSystemModule(h.serverConfig.NetworkAPI, h.serverConfig.SystemAPI) case "author": srvc = modules.NewAuthorModule(h.serverConfig.CoreAPI, h.serverConfig.RuntimeAPI, h.serverConfig.TransactionQueueAPI) case "chain": diff --git a/dot/rpc/http_test.go b/dot/rpc/http_test.go index d5f6765348..c30d46395c 100644 --- a/dot/rpc/http_test.go +++ b/dot/rpc/http_test.go @@ -23,16 +23,23 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/core" + "github.com/ChainSafe/gossamer/dot/system" + "github.com/ChainSafe/gossamer/dot/types" "github.com/stretchr/testify/require" ) func TestNewHTTPServer(t *testing.T) { coreAPI := core.NewTestService(t, nil) + si := &types.SystemInfo{ + SystemName: "gossamer", + } + sysAPI := system.NewService(si) cfg := &HTTPServerConfig{ - Modules: []string{"system"}, - RPCPort: 8545, - RPCAPI: NewService(), - CoreAPI: coreAPI, + Modules: []string{"system"}, + RPCPort: 8545, + RPCAPI: NewService(), + CoreAPI: coreAPI, + SystemAPI: sysAPI, } s := NewHTTPServer(cfg) diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go index deb22adb48..fb116453bb 100644 --- a/dot/rpc/modules/api.go +++ b/dot/rpc/modules/api.go @@ -59,3 +59,11 @@ type RPCAPI interface { type RuntimeAPI interface { ValidateTransaction(e types.Extrinsic) (*transaction.Validity, error) } + +// SystemAPI is the interface for handling system methods +type SystemAPI interface { + SystemName() string + SystemVersion() string + NodeName() string + Properties() map[string]interface{} +} diff --git a/dot/rpc/modules/system.go b/dot/rpc/modules/system.go index a8b4f2976e..db1415ef89 100644 --- a/dot/rpc/modules/system.go +++ b/dot/rpc/modules/system.go @@ -22,12 +22,10 @@ import ( "github.com/ChainSafe/gossamer/lib/common" ) -// NOT_IMPLEMENTED used as placeholder for not implemented yet funcs -const NOT_IMPLEMENTED = "not yet implemented" - // SystemModule is an RPC module providing access to core API points type SystemModule struct { networkAPI NetworkAPI + systemAPI SystemAPI } // EmptyRequest represents an RPC request with no fields @@ -51,50 +49,35 @@ type SystemPeersResponse struct { Peers []common.PeerInfo `json:"peers"` } -// SystemPropertiesResponse struct to marshal json -type SystemPropertiesResponse struct { - Ss58Format int `json:"ss58Format"` - TokenDecimals int `json:"tokenDecimals"` - TokenSymbol string `json:"tokenSymbol"` -} - // NewSystemModule creates a new API instance -func NewSystemModule(net NetworkAPI) *SystemModule { +func NewSystemModule(net NetworkAPI, sys SystemAPI) *SystemModule { return &SystemModule{ networkAPI: net, // TODO: migrate to network state + systemAPI: sys, } } // Chain returns the runtime chain -func (sm *SystemModule) Chain(r *http.Request, req *EmptyRequest, res *StringResponse) error { - // TODO implement lookup of value - *res = "Development" +func (sm *SystemModule) Chain(r *http.Request, req *EmptyRequest, res *string) error { + *res = sm.systemAPI.NodeName() return nil } // Name returns the runtime name -func (sm *SystemModule) Name(r *http.Request, req *EmptyRequest, res *StringResponse) error { - // TODO implement lookup of value - *res = "gossamer v0.0" +func (sm *SystemModule) Name(r *http.Request, req *EmptyRequest, res *string) error { + *res = sm.systemAPI.SystemName() return nil } // Properties returns the runtime properties -func (sm *SystemModule) Properties(r *http.Request, req *EmptyRequest, res *SystemPropertiesResponse) error { - // TODO implement lookup of this value - sp := SystemPropertiesResponse{ - Ss58Format: 2, - TokenDecimals: 12, - TokenSymbol: "KSM", - } - *res = sp +func (sm *SystemModule) Properties(r *http.Request, req *EmptyRequest, res *interface{}) error { + *res = sm.systemAPI.Properties() return nil } // Version returns the runtime version -func (sm *SystemModule) Version(r *http.Request, req *EmptyRequest, res *StringResponse) error { - // TODO implement lookup of this - *res = "0.0.0" +func (sm *SystemModule) Version(r *http.Request, req *EmptyRequest, res *string) error { + *res = sm.systemAPI.SystemVersion() return nil } diff --git a/dot/rpc/modules/system_test.go b/dot/rpc/modules/system_test.go index 63db6b3618..86f3ee1741 100644 --- a/dot/rpc/modules/system_test.go +++ b/dot/rpc/modules/system_test.go @@ -59,7 +59,7 @@ func newNetworkService(t *testing.T) *network.Service { // Test RPC's System.Health() response func TestSystemModule_Health(t *testing.T) { net := newNetworkService(t) - sys := NewSystemModule(net) + sys := NewSystemModule(net, nil) res := &SystemHealthResponse{} sys.Health(nil, nil, res) @@ -72,7 +72,7 @@ func TestSystemModule_Health(t *testing.T) { // Test RPC's System.NetworkState() response func TestSystemModule_NetworkState(t *testing.T) { net := newNetworkService(t) - sys := NewSystemModule(net) + sys := NewSystemModule(net, nil) res := &SystemNetworkStateResponse{} sys.NetworkState(nil, nil, res) @@ -87,7 +87,7 @@ func TestSystemModule_NetworkState(t *testing.T) { // Test RPC's System.Peers() response func TestSystemModule_Peers(t *testing.T) { net := newNetworkService(t) - sys := NewSystemModule(net) + sys := NewSystemModule(net, nil) res := &SystemPeersResponse{} sys.Peers(nil, nil, res) diff --git a/dot/rpc/service.go b/dot/rpc/service.go index 8843f8e047..b1e179b666 100644 --- a/dot/rpc/service.go +++ b/dot/rpc/service.go @@ -30,7 +30,9 @@ type Service struct { // NewService create a new instance of Service func NewService() *Service { - return &Service{rpcMethods: []string{}} + return &Service{ + rpcMethods: []string{}, + } } // Methods returns list of methods available via RPC call diff --git a/dot/rpc/service_test.go b/dot/rpc/service_test.go index c12e74b108..987a755717 100644 --- a/dot/rpc/service_test.go +++ b/dot/rpc/service_test.go @@ -32,7 +32,7 @@ func TestService_Methods(t *testing.T) { qtyAuthorMethods := 6 rpcService := NewService() - sysMod := modules.NewSystemModule(nil) + sysMod := modules.NewSystemModule(nil, nil) rpcService.BuildMethodNames(sysMod, "system") m := rpcService.Methods() require.Equal(t, qtySystemMethods, len(m)) // check to confirm quantity for methods is correct diff --git a/dot/rpc/websocket_test.go b/dot/rpc/websocket_test.go index 923d2585cd..b0ae6e17b4 100644 --- a/dot/rpc/websocket_test.go +++ b/dot/rpc/websocket_test.go @@ -8,6 +8,8 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/core" + "github.com/ChainSafe/gossamer/dot/system" + "github.com/ChainSafe/gossamer/dot/types" "github.com/gorilla/websocket" "github.com/stretchr/testify/require" ) @@ -17,7 +19,7 @@ var testCalls = []struct { call []byte expected []byte }{ - {[]byte(`{"jsonrpc":"2.0","method":"system_name","params":[],"id":1}`), []byte(`{"id":1,"jsonrpc":"2.0","result":"gossamer v0.0"}` + "\n")}, // working request + {[]byte(`{"jsonrpc":"2.0","method":"system_name","params":[],"id":1}`), []byte(`{"id":1,"jsonrpc":"2.0","result":"gossamer"}` + "\n")}, // working request {[]byte(`{"jsonrpc":"2.0","method":"unknown","params":[],"id":1}`), []byte(`{"error":{"code":-32000,"data":null,"message":"rpc error method unknown not found"},"id":1,"jsonrpc":"2.0"}` + "\n")}, // unknown method {[]byte{}, []byte(`{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid request"},"id":null}` + "\n")}, // empty request {[]byte(`{"jsonrpc":"2.0","method":"chain_subscribeNewHeads","params":[],"id":1}`), []byte(`{"jsonrpc":"2.0","result":1,"id":1}` + "\n")}, @@ -25,6 +27,10 @@ var testCalls = []struct { func TestNewWebSocketServer(t *testing.T) { coreAPI := core.NewTestService(t, nil) + si := &types.SystemInfo{ + SystemName: "gossamer", + } + sysAPI := system.NewService(si) cfg := &HTTPServerConfig{ Modules: []string{"system", "chain"}, RPCPort: 8545, @@ -32,6 +38,7 @@ func TestNewWebSocketServer(t *testing.T) { WSEnabled: true, RPCAPI: NewService(), CoreAPI: coreAPI, + SystemAPI: sysAPI, } s := NewHTTPServer(cfg) diff --git a/dot/services.go b/dot/services.go index a71cd0ffd6..d062dd3abf 100644 --- a/dot/services.go +++ b/dot/services.go @@ -24,6 +24,8 @@ import ( "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/rpc" "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/dot/system" + "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" log "github.com/ChainSafe/log15" @@ -144,7 +146,7 @@ func createNetworkService(cfg *Config, stateSrvc *state.Service, coreMsgs chan n // RPC Service // createRPCService creates the RPC service from the provided core configuration -func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Service, networkSrvc *network.Service, rt *runtime.Runtime) *rpc.HTTPServer { +func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Service, networkSrvc *network.Service, rt *runtime.Runtime, sysSrvc *system.Service) *rpc.HTTPServer { log.Info( "[dot] creating rpc service...", "host", cfg.RPC.Host, @@ -153,7 +155,6 @@ func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Serv "ws port", cfg.RPC.WSPort, ) rpcService := rpc.NewService() - rpcConfig := &rpc.HTTPServerConfig{ BlockAPI: stateSrvc.Block, StorageAPI: stateSrvc.Storage, @@ -162,6 +163,7 @@ func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Serv RuntimeAPI: rt, TransactionQueueAPI: stateSrvc.TransactionQueue, RPCAPI: rpcService, + SystemAPI: sysSrvc, Host: cfg.RPC.Host, RPCPort: cfg.RPC.Port, WSEnabled: cfg.RPC.WSEnabled, @@ -171,3 +173,9 @@ func createRPCService(cfg *Config, stateSrvc *state.Service, coreSrvc *core.Serv return rpc.NewHTTPServer(rpcConfig) } + +// System service +// creates a service for providing system related information +func createSystemService(cfg *types.SystemInfo) *system.Service { + return system.NewService(cfg) +} diff --git a/dot/services_test.go b/dot/services_test.go index 6a1c9a26c3..27f04d18ba 100644 --- a/dot/services_test.go +++ b/dot/services_test.go @@ -23,7 +23,6 @@ import ( "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/utils" - "github.com/stretchr/testify/require" ) @@ -150,7 +149,9 @@ func TestCreateRPCService(t *testing.T) { networkSrvc := &network.Service{} // TODO: rpc service without network service - rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, rt) + sysSrvc := createSystemService(&cfg.System) + + rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, rt, sysSrvc) require.Nil(t, err) // TODO: improve dot tests #687 diff --git a/dot/system/service.go b/dot/system/service.go new file mode 100644 index 0000000000..c192678da2 --- /dev/null +++ b/dot/system/service.go @@ -0,0 +1,61 @@ +// Copyright 2020 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 system + +import "github.com/ChainSafe/gossamer/dot/types" + +// Service struct to hold rpc service data +type Service struct { + systemInfo *types.SystemInfo +} + +// NewService create a new instance of Service +func NewService(si *types.SystemInfo) *Service { + return &Service{ + systemInfo: si, + } +} + +// SystemName returns the app name +func (s *Service) SystemName() string { + return s.systemInfo.SystemName +} + +// SystemVersion returns the app version +func (s *Service) SystemVersion() string { + return s.systemInfo.SystemVersion +} + +// NodeName returns the nodeName (chain name) +func (s *Service) NodeName() string { + return s.systemInfo.NodeName +} + +// Properties Get a custom set of properties as a JSON object, defined in the chain spec. +func (s *Service) Properties() map[string]interface{} { + return s.systemInfo.SystemProperties +} + +// Start implements Service interface +func (s *Service) Start() error { + return nil +} + +// Stop implements Service interface +func (s *Service) Stop() error { + return nil +} diff --git a/dot/system/service_test.go b/dot/system/service_test.go new file mode 100644 index 0000000000..3f1b78b487 --- /dev/null +++ b/dot/system/service_test.go @@ -0,0 +1,82 @@ +// Copyright 2020 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 system + +import ( + "testing" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/stretchr/testify/require" +) + +func TestService_NodeName(t *testing.T) { + svc := newTestService() + + name := svc.NodeName() + require.Equal(t, "gssmr", name) +} + +func TestService_SystemName(t *testing.T) { + svc := newTestService() + + name := svc.SystemName() + require.Equal(t, "gossamer", name) +} + +func TestService_SystemVersion(t *testing.T) { + svc := newTestService() + ver := svc.SystemVersion() + require.Equal(t, "0.0.1", ver) +} + +func TestService_Properties(t *testing.T) { + expected := make(map[string]interface{}) + expected["ss58Format"] = 2 + expected["tokenDecimals"] = 12 + expected["tokenSymbol"] = "KSM" + + svc := newTestService() + props := svc.Properties() + require.Equal(t, expected, props) +} + +func TestService_Start(t *testing.T) { + svc := newTestService() + err := svc.Start() + require.NoError(t, err) +} + +func TestService_Stop(t *testing.T) { + svc := newTestService() + err := svc.Stop() + require.NoError(t, err) +} + +func newTestService() *Service { + sysProps := make(map[string]interface{}) + sysProps["ss58Format"] = 2 + sysProps["tokenDecimals"] = 12 + sysProps["tokenSymbol"] = "KSM" + + sysInfo := &types.SystemInfo{ + SystemName: "gossamer", + SystemVersion: "0.0.1", + NodeName: "gssmr", + SystemProperties: sysProps, + } + return NewService(sysInfo) +} diff --git a/dot/types/system.go b/dot/types/system.go new file mode 100644 index 0000000000..c71b4d00f8 --- /dev/null +++ b/dot/types/system.go @@ -0,0 +1,9 @@ +package types + +// SystemInfo struct to hold system related information +type SystemInfo struct { + SystemName string + SystemVersion string + NodeName string + SystemProperties map[string]interface{} +} diff --git a/dot/utils.go b/dot/utils.go index d83bc4edee..11c456dd5f 100644 --- a/dot/utils.go +++ b/dot/utils.go @@ -47,6 +47,7 @@ func NewTestConfig(t *testing.T) *Config { Core: GssmrConfig().Core, Network: GssmrConfig().Network, RPC: GssmrConfig().RPC, + System: GssmrConfig().System, } } diff --git a/node/gssmr/config.toml b/node/gssmr/config.toml index dcc8072270..256375f6fe 100644 --- a/node/gssmr/config.toml +++ b/node/gssmr/config.toml @@ -27,3 +27,4 @@ port = 8545 host = "localhost" modules = ["system", "author", "chain", "state"] ws-port = 8546 +ws-enabled = false diff --git a/node/ksmcc/config.toml b/node/ksmcc/config.toml index eab0aa9261..81914644ff 100644 --- a/node/ksmcc/config.toml +++ b/node/ksmcc/config.toml @@ -27,3 +27,4 @@ port = 8545 host = "localhost" modules = ["system"] ws-port = 8546 +ws-enabled = false