diff --git a/.vscode/launch.json b/.vscode/launch.json index f3f9e09b..b7cbc8e3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,19 +24,6 @@ "env": {}, "showLog": true, "trace": "verbose" - }, - { - "name": "Cascade E2E (test-cascade)", - "type": "go", - "request": "launch", - "mode": "test", - "program": "${workspaceFolder}/tests/system", - "cwd": "${workspaceFolder}/tests/system", - "args": ["-test.run", "TestCascadeE2E", "-test.v"], - "buildFlags": "-tags=system_test", - "env": {}, - "showLog": true, - "trace": "verbose" - } + }, ] -} +} \ No newline at end of file diff --git a/go.mod b/go.mod index 6d1cd2c7..ae837e25 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,6 @@ require ( github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect github.com/DataDog/datadog-go v4.8.3+incompatible // indirect - github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/go.sum b/go.sum index 3b16ff80..ab08b5ae 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,6 @@ github.com/LumeraProtocol/lumera v1.8.6-rc2 h1:o4f3HOpmpk6VU+PiFOExx6F6doffLCKJU github.com/LumeraProtocol/lumera v1.8.6-rc2/go.mod h1:DcG+PermGhl5uA51VaSA0EC+FXpDVm2XgifmYL9jJvE= github.com/LumeraProtocol/rq-go v0.2.1 h1:8B3UzRChLsGMmvZ+UVbJsJj6JZzL9P9iYxbdUwGsQI4= github.com/LumeraProtocol/rq-go v0.2.1/go.mod h1:APnKCZRh1Es2Vtrd2w4kCLgAyaL5Bqrkz/BURoRJ+O8= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= diff --git a/p2p/kademlia/dht.go b/p2p/kademlia/dht.go index 3a958ec7..2c744f2a 100644 --- a/p2p/kademlia/dht.go +++ b/p2p/kademlia/dht.go @@ -2114,12 +2114,6 @@ func (s *DHT) IterateBatchStore(ctx context.Context, values [][]byte, typ int, i knownNodes := make(map[string]*Node) hashes := make([][]byte, len(values)) - // In integration tests we want to exercise the Cascade pipeline and local - // storage without depending on a fully healthy DHT graph. If we cannot - // find any candidate peers to send store RPCs to, we treat this as a - // no-op success instead of failing the whole operation. - isIntegrationTest := os.Getenv("INTEGRATION_TEST") == "true" - { f := logtrace.Fields{logtrace.FieldModule: "dht", "task_id": id, "keys": len(values), "len_nodes": len(s.ht.nodes()), logtrace.FieldRole: "client"} if o := logtrace.OriginFromContext(ctx); o != "" { @@ -2149,18 +2143,6 @@ func (s *DHT) IterateBatchStore(ctx context.Context, values [][]byte, typ int, i successful := 0 logtrace.Debug(ctx, "Iterate batch store: dispatching to nodes", logtrace.Fields{"task_id": id, "nodes": len(knownNodes)}) - - // If there are no candidate nodes and we're running under the integration - // test harness, skip the network fan-out and report success so higher - // layers (Cascade tests) can proceed. - if len(knownNodes) == 0 && isIntegrationTest { - logtrace.Info(ctx, "dht: batch store skipped (no candidate nodes in integration test)", logtrace.Fields{ - logtrace.FieldModule: "dht", - "task_id": id, - "keys": len(values), - }) - return nil - } storeResponses := s.batchStoreNetwork(ctx, values, knownNodes, storageMap, typ) for response := range storeResponses { requests++ diff --git a/pkg/lumera/client.go b/pkg/lumera/client.go index bcd93a71..2e25877c 100644 --- a/pkg/lumera/client.go +++ b/pkg/lumera/client.go @@ -10,21 +10,19 @@ import ( "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/bank" "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/node" "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode" - "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode_msg" "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/tx" ) type lumeraClient struct { - cfg *Config - authMod auth.Module - actionMod action.Module - actionMsgMod action_msg.Module - bankMod bank.Module - supernodeMod supernode.Module - supernodeMsgMod supernode_msg.Module - txMod tx.Module - nodeMod node.Module - conn Connection + cfg *Config + authMod auth.Module + actionMod action.Module + actionMsgMod action_msg.Module + bankMod bank.Module + supernodeMod supernode.Module + txMod tx.Module + nodeMod node.Module + conn Connection } func newClient(ctx context.Context, cfg *Config) (Client, error) { @@ -95,31 +93,16 @@ func newClient(ctx context.Context, cfg *Config) (Client, error) { return nil, err } - supernodeMsgModule, err := supernode_msg.NewModule( - conn.GetConn(), - authModule, - txModule, - supernodeModule, - cfg.keyring, - cfg.KeyName, - cfg.ChainID, - ) - if err != nil { - conn.Close() - return nil, err - } - return &lumeraClient{ - cfg: cfg, - authMod: authModule, - actionMod: actionModule, - actionMsgMod: actionMsgModule, - bankMod: bankModule, - supernodeMod: supernodeModule, - supernodeMsgMod: supernodeMsgModule, - txMod: txModule, - nodeMod: nodeModule, - conn: conn, + cfg: cfg, + authMod: authModule, + actionMod: actionModule, + actionMsgMod: actionMsgModule, + bankMod: bankModule, + supernodeMod: supernodeModule, + txMod: txModule, + nodeMod: nodeModule, + conn: conn, }, nil } @@ -143,10 +126,6 @@ func (c *lumeraClient) SuperNode() supernode.Module { return c.supernodeMod } -func (c *lumeraClient) SuperNodeMsg() supernode_msg.Module { - return c.supernodeMsgMod -} - func (c *lumeraClient) Tx() tx.Module { return c.txMod } diff --git a/pkg/lumera/codec/encoding.go b/pkg/lumera/codec/encoding.go index 0d295241..e38c9cd3 100644 --- a/pkg/lumera/codec/encoding.go +++ b/pkg/lumera/codec/encoding.go @@ -4,7 +4,6 @@ import ( "sync" actiontypes "github.com/LumeraProtocol/lumera/x/action/v1/types" - sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -50,7 +49,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { cryptocodec.RegisterInterfaces(registry) authtypes.RegisterInterfaces(registry) actiontypes.RegisterInterfaces(registry) - sntypes.RegisterInterfaces(registry) + // Add more interface registrations here as you add more modules } // GetEncodingConfig returns the standard encoding config for Lumera client diff --git a/pkg/lumera/interface.go b/pkg/lumera/interface.go index 05c91719..2fb25c13 100644 --- a/pkg/lumera/interface.go +++ b/pkg/lumera/interface.go @@ -10,7 +10,6 @@ import ( "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/bank" "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/node" "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode" - "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode_msg" "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/tx" ) @@ -20,7 +19,6 @@ type Client interface { Action() action.Module ActionMsg() action_msg.Module SuperNode() supernode.Module - SuperNodeMsg() supernode_msg.Module Bank() bank.Module Tx() tx.Module Node() node.Module diff --git a/pkg/lumera/modules/supernode_msg/impl.go b/pkg/lumera/modules/supernode_msg/impl.go deleted file mode 100644 index feb4aec1..00000000 --- a/pkg/lumera/modules/supernode_msg/impl.go +++ /dev/null @@ -1,93 +0,0 @@ -package supernode_msg - -import ( - "context" - "fmt" - "strings" - "sync" - - sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" - "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/auth" - snquery "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode" - txmod "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/tx" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - sdktypes "github.com/cosmos/cosmos-sdk/types" - sdktx "github.com/cosmos/cosmos-sdk/types/tx" - "google.golang.org/grpc" -) - -// module implements the Module interface for supernode-related transactions. -type module struct { - client sntypes.MsgClient - query snquery.Module - txHelper *txmod.TxHelper - mu sync.Mutex -} - -// newModule creates a new supernode_msg module instance. -func newModule( - conn *grpc.ClientConn, - authmodule auth.Module, - txmodule txmod.Module, - supernodeQuery snquery.Module, - kr keyring.Keyring, - keyName string, - chainID string, -) (Module, error) { - if conn == nil { - return nil, fmt.Errorf("connection cannot be nil") - } - if authmodule == nil { - return nil, fmt.Errorf("auth module cannot be nil") - } - if txmodule == nil { - return nil, fmt.Errorf("tx module cannot be nil") - } - if supernodeQuery == nil { - return nil, fmt.Errorf("supernode query module cannot be nil") - } - if kr == nil { - return nil, fmt.Errorf("keyring cannot be nil") - } - if strings.TrimSpace(keyName) == "" { - return nil, fmt.Errorf("key name cannot be empty") - } - if strings.TrimSpace(chainID) == "" { - return nil, fmt.Errorf("chain ID cannot be empty") - } - - return &module{ - client: sntypes.NewMsgClient(conn), - query: supernodeQuery, - txHelper: txmod.NewTxHelperWithDefaults(authmodule, txmodule, chainID, keyName, kr), - }, nil -} - -// ReportMetrics resolves on-chain supernode info and submits a -// MsgReportSupernodeMetrics transaction containing the provided metrics. -func (m *module) ReportMetrics(ctx context.Context, identity string, metrics sntypes.SupernodeMetrics) (*sdktx.BroadcastTxResponse, error) { - identity = strings.TrimSpace(identity) - if identity == "" { - return nil, fmt.Errorf("identity is empty") - } - - m.mu.Lock() - defer m.mu.Unlock() - - // Resolve validator and supernode account using the query module. - snInfo, err := m.query.GetSupernodeWithLatestAddress(ctx, identity) - if err != nil { - return nil, fmt.Errorf("failed to resolve supernode info: %w", err) - } - if snInfo == nil || snInfo.ValidatorAddress == "" || snInfo.SupernodeAccount == "" { - return nil, fmt.Errorf("incomplete supernode info for identity %s", identity) - } - - return m.txHelper.ExecuteTransaction(ctx, func(_ string) (sdktypes.Msg, error) { - return &sntypes.MsgReportSupernodeMetrics{ - ValidatorAddress: snInfo.ValidatorAddress, - SupernodeAccount: snInfo.SupernodeAccount, - Metrics: metrics, - }, nil - }) -} diff --git a/pkg/lumera/modules/supernode_msg/interface.go b/pkg/lumera/modules/supernode_msg/interface.go deleted file mode 100644 index 7b5c837b..00000000 --- a/pkg/lumera/modules/supernode_msg/interface.go +++ /dev/null @@ -1,37 +0,0 @@ -//go:generate mockgen -destination=supernode_msg_mock.go -package=supernode_msg -source=interface.go -package supernode_msg - -import ( - "context" - - "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/auth" - "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode" - "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/tx" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - sdktx "github.com/cosmos/cosmos-sdk/types/tx" - "google.golang.org/grpc" - - sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" -) - -// Module defines the interface for sending supernode-related transactions, -// such as reporting metrics for LEP-4 compliance. -type Module interface { - // ReportMetrics resolves on-chain supernode info for the given identity, - // builds a MsgReportSupernodeMetrics, and broadcasts it. - ReportMetrics(ctx context.Context, identity string, metrics sntypes.SupernodeMetrics) (*sdktx.BroadcastTxResponse, error) -} - -// NewModule constructs a supernode_msg Module using the shared auth and tx -// modules, the supernode query module, and keyring configuration. -func NewModule( - conn *grpc.ClientConn, - authmod auth.Module, - txmod tx.Module, - supernodeQuery supernode.Module, - kr keyring.Keyring, - keyName string, - chainID string, -) (Module, error) { - return newModule(conn, authmod, txmod, supernodeQuery, kr, keyName, chainID) -} diff --git a/pkg/testutil/lumera.go b/pkg/testutil/lumera.go index a76ca2cd..4258599a 100644 --- a/pkg/testutil/lumera.go +++ b/pkg/testutil/lumera.go @@ -12,7 +12,6 @@ import ( bankmod "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/bank" "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/node" "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode" - "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode_msg" "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/tx" sdkmath "cosmossdk.io/math" @@ -26,16 +25,15 @@ import ( // MockLumeraClient implements the lumera.Client interface for testing purposes type MockLumeraClient struct { - authMod *MockAuthModule - actionMod *MockActionModule - actionMsgMod *MockActionMsgModule - bankMod *MockBankModule - supernodeMod *MockSupernodeModule - supernodeMsgMod supernode_msg.Module - txMod *MockTxModule - nodeMod *MockNodeModule - kr keyring.Keyring - addresses []string // Store node addresses for testing + authMod *MockAuthModule + actionMod *MockActionModule + actionMsgMod *MockActionMsgModule + bankMod *MockBankModule + supernodeMod *MockSupernodeModule + txMod *MockTxModule + nodeMod *MockNodeModule + kr keyring.Keyring + addresses []string // Store node addresses for testing } // NewMockLumeraClient creates a new mock Lumera client for testing @@ -44,21 +42,19 @@ func NewMockLumeraClient(kr keyring.Keyring, addresses []string) (lumera.Client, actionMsgMod := &MockActionMsgModule{} bankMod := &MockBankModule{} supernodeMod := &MockSupernodeModule{addresses: addresses} - supernodeMsgMod := &MockSupernodeMsgModule{} txMod := &MockTxModule{} nodeMod := &MockNodeModule{} return &MockLumeraClient{ - authMod: &MockAuthModule{}, - actionMod: actionMod, - actionMsgMod: actionMsgMod, - bankMod: bankMod, - supernodeMod: supernodeMod, - supernodeMsgMod: supernodeMsgMod, - txMod: txMod, - nodeMod: nodeMod, - kr: kr, - addresses: addresses, + authMod: &MockAuthModule{}, + actionMod: actionMod, + actionMsgMod: actionMsgMod, + bankMod: bankMod, + supernodeMod: supernodeMod, + txMod: txMod, + nodeMod: nodeMod, + kr: kr, + addresses: addresses, }, nil } @@ -87,11 +83,6 @@ func (c *MockLumeraClient) SuperNode() supernode.Module { return c.supernodeMod } -// SuperNodeMsg returns the SuperNodeMsg module client -func (c *MockLumeraClient) SuperNodeMsg() supernode_msg.Module { - return c.supernodeMsgMod -} - // Tx returns the Transaction module client func (c *MockLumeraClient) Tx() tx.Module { return c.txMod @@ -174,9 +165,6 @@ type MockSupernodeModule struct { addresses []string } -// MockSupernodeMsgModule implements the supernode_msg.Module interface for testing. -type MockSupernodeMsgModule struct{} - func (m *MockSupernodeModule) GetTopSuperNodesForBlock(ctx context.Context, req *supernodeTypes.QueryGetTopSuperNodesForBlockRequest) (*supernodeTypes.QueryGetTopSuperNodesForBlockResponse, error) { return &supernodeTypes.QueryGetTopSuperNodesForBlockResponse{}, nil } @@ -209,11 +197,6 @@ func (m *MockSupernodeModule) ListSuperNodes(ctx context.Context) (*supernodeTyp return &supernodeTypes.QueryListSuperNodesResponse{}, nil } -// ReportMetrics mocks broadcasting a metrics report transaction. -func (m *MockSupernodeMsgModule) ReportMetrics(ctx context.Context, identity string, metrics supernodeTypes.SupernodeMetrics) (*sdktx.BroadcastTxResponse, error) { - return &sdktx.BroadcastTxResponse{}, nil -} - // MockTxModule implements the tx.Module interface for testing type MockTxModule struct{} diff --git a/sdk/action/client.go b/sdk/action/client.go index 3016d6ab..46966064 100644 --- a/sdk/action/client.go +++ b/sdk/action/client.go @@ -199,7 +199,7 @@ func (c *ClientImpl) GetSupernodeStatus(ctx context.Context, supernodeAddress st lumeraSupernode := lumera.Supernode{ CosmosAddress: supernodeAddress, GrpcEndpoint: supernodeInfo.LatestAddress, - State: lumera.ParseSupernodeState(supernodeInfo.CurrentState), + State: lumera.SUPERNODE_STATE_ACTIVE, // Assume active since we're querying } // Create network client factory diff --git a/sdk/adapters/lumera/adapter.go b/sdk/adapters/lumera/adapter.go index 7a706ea6..8137a4b9 100644 --- a/sdk/adapters/lumera/adapter.go +++ b/sdk/adapters/lumera/adapter.go @@ -403,15 +403,15 @@ func toSdkSupernodes(resp *sntypes.QueryGetTopSuperNodesForBlockResponse) []Supe continue } - state := ParseSupernodeState(latestState.State.String()) - if state == SUPERNODE_STATE_UNSPECIFIED { + // Check if the latest state is active + if latestState.State.String() != string(SUPERNODE_STATE_ACTIVE) { continue } result = append(result, Supernode{ CosmosAddress: strings.TrimSpace(sn.SupernodeAccount), GrpcEndpoint: strings.TrimSpace(ipAddress), - State: state, + State: SUPERNODE_STATE_ACTIVE, }) } return result diff --git a/sdk/adapters/lumera/types.go b/sdk/adapters/lumera/types.go index c560d3fc..238a9e77 100644 --- a/sdk/adapters/lumera/types.go +++ b/sdk/adapters/lumera/types.go @@ -21,7 +21,6 @@ const ( SUPERNODE_STATE_DISABLED SUPERNODE_STATE = "SUPERNODE_STATE_DISABLED" SUPERNODE_STATE_STOPPED SUPERNODE_STATE = "SUPERNODE_STATE_STOPPED" SUPERNODE_STATE_PENALIZED SUPERNODE_STATE = "SUPERNODE_STATE_PENALIZED" - SUPERNODE_STATE_POSTPONED SUPERNODE_STATE = "SUPERNODE_STATE_POSTPONED" ) // Action represents an action registered on the Lumera blockchain @@ -57,17 +56,3 @@ func (s Supernodes) String() string { result += "]" return result } - -// ParseSupernodeState normalizes a raw state string into the SUPERNODE_STATE enum. -func ParseSupernodeState(state string) SUPERNODE_STATE { - switch SUPERNODE_STATE(state) { - case SUPERNODE_STATE_ACTIVE, - SUPERNODE_STATE_DISABLED, - SUPERNODE_STATE_STOPPED, - SUPERNODE_STATE_PENALIZED, - SUPERNODE_STATE_POSTPONED: - return SUPERNODE_STATE(state) - default: - return SUPERNODE_STATE_UNSPECIFIED - } -} diff --git a/supernode/cmd/start.go b/supernode/cmd/start.go index 369aeebe..994d3d47 100644 --- a/supernode/cmd/start.go +++ b/supernode/cmd/start.go @@ -22,7 +22,6 @@ import ( cascadeService "github.com/LumeraProtocol/supernode/v2/supernode/cascade" "github.com/LumeraProtocol/supernode/v2/supernode/config" statusService "github.com/LumeraProtocol/supernode/v2/supernode/status" - supernodeMetrics "github.com/LumeraProtocol/supernode/v2/supernode/supernode_metrics" "github.com/LumeraProtocol/supernode/v2/supernode/transport/gateway" cascadeRPC "github.com/LumeraProtocol/supernode/v2/supernode/transport/grpc/cascade" server "github.com/LumeraProtocol/supernode/v2/supernode/transport/grpc/status" @@ -148,19 +147,6 @@ The supernode will connect to the Lumera network and begin participating in the // Create supernode status service with injected tracker statusSvc := statusService.NewSupernodeStatusService(p2pService, lumeraClient, appConfig, tr) - metricsCollector := supernodeMetrics.NewCollector( - statusSvc, - lumeraClient, - appConfig.SupernodeConfig.Identity, - Version, - p2pService, - kr, - appConfig.SupernodeConfig.Port, - appConfig.P2PConfig.Port, - appConfig.SupernodeConfig.GatewayPort, - ) - logtrace.Info(ctx, "Metrics collection enabled", logtrace.Fields{}) - // Create supernode server supernodeServer := server.NewSupernodeServer(statusSvc) @@ -193,10 +179,7 @@ The supernode will connect to the Lumera network and begin participating in the // Start the services using the standard runner and capture exit servicesErr := make(chan error, 1) - go func() { - services := []service{grpcServer, cService, p2pService, gatewayServer, metricsCollector} - servicesErr <- RunServices(ctx, services...) - }() + go func() { servicesErr <- RunServices(ctx, grpcServer, cService, p2pService, gatewayServer) }() // Set up signal handling for graceful shutdown sigCh := make(chan os.Signal, 1) diff --git a/supernode/supernode_metrics/health_checks.go b/supernode/supernode_metrics/health_checks.go deleted file mode 100644 index ae202cfa..00000000 --- a/supernode/supernode_metrics/health_checks.go +++ /dev/null @@ -1,226 +0,0 @@ -package supernode_metrics - -import ( - "context" - "fmt" - "net" - "net/http" - "strings" - "time" - - "github.com/LumeraProtocol/lumera/x/lumeraid/securekeyx" - "github.com/LumeraProtocol/supernode/v2/pkg/logtrace" - lumeravalid "github.com/LumeraProtocol/supernode/v2/pkg/lumera" - ltc "github.com/LumeraProtocol/supernode/v2/pkg/net/credentials" - grpcclient "github.com/LumeraProtocol/supernode/v2/pkg/net/grpc/client" - "google.golang.org/grpc/health/grpc_health_v1" -) - -// checkP2PService performs a lightweight health check against the local P2P -// service using a self-directed DHT ping. -// -// The return value is a normalized "health score" in the range [0, 1]: -// - 1.0 indicates healthy / reachable -// - 0.0 indicates unhealthy / unreachable -// -// For now this is stubbed to always return 1.0 but is shaped for richer -// checks (latency, error rates, etc.) in the future. -func (hm *Collector) checkP2PService(ctx context.Context) float64 { - identity := strings.TrimSpace(hm.identity) - if identity == "" || hm.keyring == nil || hm.lumeraClient == nil { - logtrace.Warn(ctx, "P2P health check skipped: missing identity/keyring/client", nil) - return 0.0 - } - - host := hm.getPublicIP(ctx) - if host == "" { - host = "127.0.0.1" - } - target := net.JoinHostPort(host, fmt.Sprintf("%d", P2PPort)) - if hm.p2pPort != 0 { - target = net.JoinHostPort(host, fmt.Sprintf("%d", hm.p2pPort)) - } - - // Build client credentials using the same ALTS/securekeyx stack as external - // P2P clients. We treat the local node as a simplenode peer dialing the - // supernode (itself) by identity. - clientCreds, err := ltc.NewClientCreds(<c.ClientOptions{ - CommonOptions: ltc.CommonOptions{ - Keyring: hm.keyring, - LocalIdentity: identity, - PeerType: securekeyx.Simplenode, - Validator: lumeravalid.NewSecureKeyExchangeValidator(hm.lumeraClient), - }, - }) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("P2P health check: failed to create client credentials: %v", err), nil) - return 0.0 - } - - lumeraTC, ok := clientCreds.(*ltc.LumeraTC) - if !ok { - logtrace.Error(ctx, "P2P health check: invalid credentials type (expected *LumeraTC)", nil) - return 0.0 - } - // Remote identity is the supernode itself. - lumeraTC.SetRemoteIdentity(identity) - - checkCtx, cancel := context.WithTimeout(ctx, time.Duration(PortCheckTimeoutSeconds)*time.Second) - defer cancel() - - var d net.Dialer - rawConn, err := d.DialContext(checkCtx, "tcp", target) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("P2P health check: failed to dial %s: %v", target, err), nil) - return 0.0 - } - defer rawConn.Close() - - // Ensure the handshake cannot hang indefinitely. - _ = rawConn.SetDeadline(time.Now().UTC().Add(time.Duration(PortCheckTimeoutSeconds) * time.Second)) - - secureConn, _, err := lumeraTC.ClientHandshake(checkCtx, "", rawConn) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("P2P health check: handshake failed against %s: %v", target, err), nil) - return 0.0 - } - _ = secureConn.Close() - - // If we reach this point the node accepted a full secure handshake on the - // P2P port, which is sufficient to treat the service as healthy. - return 1.0 -} - -// checkStatusAPI performs an HTTP GET to the external /api/v1/status endpoint -// exposed by the gateway to validate that the REST API is reachable and -// functioning. -// -// Like other health checks, the result is a [0, 1] score. This function -// currently serves as a placeholder; once wired to a concrete HTTP client -// it will surface connectivity and status-code level failures. -func (hm *Collector) checkStatusAPI(ctx context.Context) float64 { - host := hm.getPublicIP(ctx) - if host == "" { - host = "127.0.0.1" - } - - port := StatusPort - if hm.gatewayPort != 0 { - port = int(hm.gatewayPort) - } - url := fmt.Sprintf("http://%s:%d/api/v1/status", host, port) - - reqCtx, cancel := context.WithTimeout(ctx, time.Duration(PortCheckTimeoutSeconds)*time.Second) - defer cancel() - - req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, url, nil) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("Status API health check: failed to build request: %v", err), nil) - return 0.0 - } - - client := &http.Client{ - Timeout: time.Duration(PortCheckTimeoutSeconds) * time.Second, - } - resp, err := client.Do(req) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("Status API health check: request failed to %s: %v", url, err), nil) - return 0.0 - } - defer resp.Body.Close() - - if resp.StatusCode >= 200 && resp.StatusCode < 300 { - return 1.0 - } - - logtrace.Error(ctx, fmt.Sprintf("Status API health check: non-success status %d from %s", resp.StatusCode, url), nil) - return 0.0 -} - -// checkGRPCService performs a gRPC self-health check against the public -// supernode gRPC endpoint using the same ALTS/TLS configuration that external -// clients use. -// -// The metric is expressed as a [0, 1] score to make it easy to aggregate or -// combine with additional health signals in the future. -func (hm *Collector) checkGRPCService(ctx context.Context) float64 { - identity := strings.TrimSpace(hm.identity) - if identity == "" || hm.keyring == nil || hm.lumeraClient == nil { - logtrace.Warn(ctx, "gRPC health check skipped: missing identity/keyring/client", nil) - return 0.0 - } - - host := hm.getPublicIP(ctx) - if host == "" { - host = "127.0.0.1" - } - port := APIPort - if hm.grpcPort != 0 { - port = int(hm.grpcPort) - } - grpcEndpoint := fmt.Sprintf("%s:%d", host, port) - - // Build client credentials mirroring external secure supernode clients. - clientCreds, err := ltc.NewClientCreds(<c.ClientOptions{ - CommonOptions: ltc.CommonOptions{ - Keyring: hm.keyring, - LocalIdentity: identity, - PeerType: securekeyx.Simplenode, - Validator: lumeravalid.NewSecureKeyExchangeValidator(hm.lumeraClient), - }, - }) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("gRPC health check: failed to create client credentials: %v", err), nil) - return 0.0 - } - - // Format address as "identity@host:port" so the ALTS layer knows which - // supernode identity we expect on the remote end (ourselves). - target := ltc.FormatAddressWithIdentity(identity, grpcEndpoint) - - checkCtx, cancel := context.WithTimeout(ctx, time.Duration(PortCheckTimeoutSeconds)*time.Second) - defer cancel() - - grpcClient := grpcclient.NewClient(clientCreds) - conn, err := grpcClient.Connect(checkCtx, target, grpcclient.DefaultClientOptions()) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("gRPC health check: connection failed to %s: %v", grpcEndpoint, err), nil) - return 0.0 - } - defer conn.Close() - - healthClient := grpc_health_v1.NewHealthClient(conn) - resp, err := healthClient.Check(checkCtx, &grpc_health_v1.HealthCheckRequest{}) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("gRPC health check: health RPC failed for %s: %v", grpcEndpoint, err), nil) - return 0.0 - } - if resp.Status != grpc_health_v1.HealthCheckResponse_SERVING { - logtrace.Error(ctx, fmt.Sprintf("gRPC health check: service not serving (status=%v) at %s", resp.Status, grpcEndpoint), nil) - return 0.0 - } - - return 1.0 -} - -// getPublicIP determines the node's public IP address from chain registration. -func (hm *Collector) getPublicIP(ctx context.Context) string { - // Get our registered IP from the blockchain - this is the source of truth. - // The SuperNode must be registered on chain to operate, so this should always work. - snInfo, err := hm.lumeraClient.SuperNode().GetSupernodeWithLatestAddress(ctx, hm.identity) - if err == nil && snInfo != nil && snInfo.LatestAddress != "" { - // Extract IP from "ip:port" format if present. - address := strings.TrimSpace(snInfo.LatestAddress) - if idx := strings.Index(address, ":"); idx > 0 { - return address[:idx] - } - return address - } - - // If we can't get IP from chain, log error and return empty. - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("failed to get IP from chain registration: %v (identity=%s)", err, hm.identity), nil) - } - - return "" -} diff --git a/supernode/supernode_metrics/metrics_collection.go b/supernode/supernode_metrics/metrics_collection.go deleted file mode 100644 index 35bcfb90..00000000 --- a/supernode/supernode_metrics/metrics_collection.go +++ /dev/null @@ -1,146 +0,0 @@ -package supernode_metrics - -import ( - "context" - "fmt" - "os" - "strconv" - "strings" - - sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" -) - -// collectMetrics gathers a snapshot of local health and resource usage and -// converts it into the canonical SupernodeMetrics protobuf message expected -// by the on-chain supernode module. -func (hm *Collector) collectMetrics(ctx context.Context) (sntypes.SupernodeMetrics, error) { - // Get status from the existing status service. - statusResp, err := hm.statusService.GetStatus(ctx, false) - if err != nil { - return sntypes.SupernodeMetrics{}, fmt.Errorf("failed to get status: %w", err) - } - - versionParts := parseVersion(hm.version) - metrics := sntypes.SupernodeMetrics{ - // 1–3: semantic version of the running supernode binary. - VersionMajor: uint32(versionParts[0]), // 1: version_major - VersionMinor: uint32(versionParts[1]), // 2: version_minor - VersionPatch: uint32(versionParts[2]), // 3: version_patch - } - - if statusResp.Resources != nil && statusResp.Resources.Cpu != nil { - metrics.CpuCoresTotal = float64(statusResp.Resources.Cpu.Cores) // 4: cpu_cores_total - metrics.CpuUsagePercent = statusResp.Resources.Cpu.UsagePercent // 5: cpu_usage_percent - } - - if statusResp.Resources != nil && statusResp.Resources.Memory != nil { - metrics.MemTotalGb = statusResp.Resources.Memory.TotalGb // 6: mem_total_gb - metrics.MemFreeGb = statusResp.Resources.Memory.AvailableGb // 8: mem_free_gb - metrics.MemUsagePercent = statusResp.Resources.Memory.UsagePercent // 7: mem_usage_percent - - if metrics.MemUsagePercent == 0 && metrics.MemTotalGb > 0 { - used := metrics.MemTotalGb - metrics.MemFreeGb - metrics.MemUsagePercent = (used / metrics.MemTotalGb) * 100 - } - } - - if statusResp.Resources != nil && len(statusResp.Resources.StorageVolumes) > 0 { - storage := statusResp.Resources.StorageVolumes[0] // 9–11: first volume is reported - const bytesToGB = 1024.0 * 1024.0 * 1024.0 - - metrics.DiskTotalGb = float64(storage.TotalBytes) / bytesToGB // 9: disk_total_gb - metrics.DiskFreeGb = float64(storage.AvailableBytes) / bytesToGB // 11: disk_free_gb - metrics.DiskUsagePercent = storage.UsagePercent // 10: disk_usage_percent - - if metrics.DiskUsagePercent == 0 && storage.TotalBytes > 0 { - used := storage.TotalBytes - storage.AvailableBytes - metrics.DiskUsagePercent = float64(used) / float64(storage.TotalBytes) * 100 - } - } - - // 12: uptime_seconds - metrics.UptimeSeconds = float64(statusResp.UptimeSeconds) - - if statusResp.Network != nil { - metrics.PeersCount = uint32(statusResp.Network.PeersCount) // 13: peers_count - - // During integration tests the status service is queried without - // P2P metrics (includeP2PMetrics=false), which leaves PeersCount at - // zero. On-chain validation requires peers_count > 0 and would mark - // test supernodes as POSTPONED immediately, preventing P2P store - // operations. To keep the metrics pipeline exercised without - // impacting production behavior, we clamp the value to 1 when - // running under the integration test harness. - if metrics.PeersCount == 0 && os.Getenv("INTEGRATION_TEST") == "true" { - metrics.PeersCount = 1 - } - } - - // 14: open_ports - metrics.OpenPorts = hm.openPorts(ctx) - - return metrics, nil -} - -// parseVersion extracts the semantic version (major, minor, patch) from a -// supernode version string, tolerating common prefixes/suffixes such as -// "v2.0.1-rc1+meta". Invalid or missing components fall back to 2.0.0. -func parseVersion(version string) [3]int { - result := [3]int{2, 0, 0} // Default to 2.0.0 - - // Clean version string. - version = strings.TrimPrefix(version, "v") - if idx := strings.IndexAny(version, "-+"); idx != -1 { - version = version[:idx] - } - - parts := strings.Split(version, ".") - for i := 0; i < len(parts) && i < 3; i++ { - if num, err := strconv.Atoi(parts[i]); err == nil { - result[i] = num - } - } - - return result -} - -// openPorts returns the set of TCP ports this node advertises as open in its -// metrics report. For each well-known port we first perform the corresponding -// self-connect health check; only ports that successfully complete their -// external-style probe are included. -func (hm *Collector) openPorts(ctx context.Context) []uint32 { - seen := make(map[uint32]struct{}, 3) - out := make([]uint32, 0, 3) - - // gRPC port (supernode service) – include only if the ALTS + gRPC health - // check succeeds. - if hm.checkGRPCService(ctx) >= 1.0 { - val := uint32(hm.grpcPort) - if _, ok := seen[val]; !ok && val != 0 { - seen[val] = struct{}{} - out = append(out, val) - } - } - - // P2P port – include only if a full ALTS handshake on the P2P socket - // succeeds. - if hm.checkP2PService(ctx) >= 1.0 { - val := uint32(hm.p2pPort) - if _, ok := seen[val]; !ok && val != 0 { - seen[val] = struct{}{} - out = append(out, val) - } - } - - // HTTP gateway / status port – include only if /api/v1/status responds - // with a successful status code. - if hm.checkStatusAPI(ctx) >= 1.0 { - val := uint32(hm.gatewayPort) - if _, ok := seen[val]; !ok && val != 0 { - seen[val] = struct{}{} - out = append(out, val) - } - } - - return out -} diff --git a/supernode/supernode_metrics/monitor_service.go b/supernode/supernode_metrics/monitor_service.go deleted file mode 100644 index da868f81..00000000 --- a/supernode/supernode_metrics/monitor_service.go +++ /dev/null @@ -1,299 +0,0 @@ -package supernode_metrics - -import ( - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/LumeraProtocol/supernode/v2/p2p" - "github.com/LumeraProtocol/supernode/v2/pkg/logtrace" - "github.com/LumeraProtocol/supernode/v2/pkg/lumera" - "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode_msg" - "github.com/LumeraProtocol/supernode/v2/supernode/status" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - - sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" -) - -const ( - // DefaultStartupDelaySeconds is a safety delay after process start before - // we begin reporting metrics, giving the node time to fully initialize. - DefaultStartupDelaySeconds = 30 - // PortCheckTimeoutSeconds bounds how long we wait when probing port - // accessibility, so a single slow check cannot stall the entire loop. - PortCheckTimeoutSeconds = 5 - - // Well-known local ports used when reporting `open_ports` metrics. - // These are defaults; individual nodes may override them via config. - // They should stay aligned with the chain's `required_open_ports` parameter. - APIPort = 4444 // Supernode gRPC port - P2PPort = 4445 // Kademlia / P2P port - StatusPort = 8002 // HTTP gateway port (grpc-gateway: /api/v1/status) -) - -// Collector manages the end-to-end supernode metrics flow: -// 1) derive configuration from on-chain params, -// 2) collect local health data from the status service and helpers, and -// 3) periodically submit SupernodeMetrics reports back to the chain. -type Collector struct { - // statusService is the single source of truth for local resource and - // process health (CPU, memory, storage, uptime, peer count, etc.). - statusService *status.SupernodeStatusService - // lumeraClient is used both to fetch module params and to construct the - // supernode message module used for ReportMetrics transactions. - lumeraClient lumera.Client - // supernodeTx exposes the Msg/tx API for submitting metrics to the chain. - supernodeTx supernode_msg.Module - // identity is the bech32 address of this supernode on-chain. - identity string - // p2pClient is reserved for future network-level health checks (P2P reachability). - p2pClient p2p.Client - // keyring holds the local signing key used when broadcasting metrics txs. - keyring keyring.Keyring - - // Control - // stopChan is closed to signal the reporting loop to exit. - stopChan chan struct{} - // wg tracks the lifetime of background goroutines to enable clean shutdowns. - wg sync.WaitGroup - - // Configuration (derived from on-chain params) - // reportInterval is the wall-clock interval between metrics reports, - // derived from the `metrics_update_interval_blocks` param and the observed block time. - reportInterval time.Duration - // version is the semantic version of this supernode binary, used to populate - // the `version_*` fields in SupernodeMetrics. - version string - - // Listener ports for this specific supernode instance. - // These are used for self-connect checks and for populating `open_ports`. - grpcPort uint16 - p2pPort uint16 - gatewayPort uint16 -} - -// NewCollector creates a new metrics collector instance. -func NewCollector( - statusSvc *status.SupernodeStatusService, - lumeraClient lumera.Client, - identity string, - version string, - p2pClient p2p.Client, - kr keyring.Keyring, - grpcPort uint16, - p2pPort uint16, - gatewayPort uint16, -) *Collector { - if grpcPort == 0 { - grpcPort = APIPort - } - if p2pPort == 0 { - p2pPort = P2PPort - } - if gatewayPort == 0 { - gatewayPort = StatusPort - } - - return &Collector{ - statusService: statusSvc, - lumeraClient: lumeraClient, - supernodeTx: lumeraClient.SuperNodeMsg(), - identity: strings.TrimSpace(identity), - p2pClient: p2pClient, - keyring: kr, - stopChan: make(chan struct{}), - version: version, - grpcPort: grpcPort, - p2pPort: p2pPort, - gatewayPort: gatewayPort, - } -} - -// Start initializes the collector's configuration (if needed) and launches the -// background reporting loop. It is safe to call only once. -func (hm *Collector) Start(ctx context.Context) error { - if hm.reportInterval <= 0 { - if hm.lumeraClient == nil { - return fmt.Errorf("lumera client is not initialized") - } - - paramsResp, err := hm.lumeraClient.SuperNode().GetParams(ctx) - if err != nil || paramsResp == nil { - logtrace.Error(ctx, fmt.Sprintf("failed to fetch supernode params for health monitor: %v", err), nil) - return fmt.Errorf("failed to fetch supernode params for health monitor: %w", err) - } - - params := paramsResp.GetParams() - intervalBlocks := params.GetMetricsUpdateIntervalBlocks() - if intervalBlocks == 0 { - return fmt.Errorf("supernode params metrics_update_interval_blocks is zero or unset") - } - - hm.reportInterval = hm.resolveReportInterval(ctx, intervalBlocks) - } - - hm.wg.Add(1) - go hm.reportingLoop(ctx) - - return nil -} - -// resolveReportInterval converts a block-based interval into a wall-clock -// duration using the current estimated block time. -func (hm *Collector) resolveReportInterval(ctx context.Context, intervalBlocks uint64) time.Duration { - if intervalBlocks == 0 { - return 0 - } - - blockTime := hm.estimateBlockTime(ctx) - if blockTime <= 0 { - blockTime = time.Second - } - - return time.Duration(intervalBlocks) * blockTime -} - -// estimateBlockTime attempts to derive a realistic average block time by -// comparing the timestamps of the latest and previous blocks. If anything -// fails, a conservative 1s fallback is used. -func (hm *Collector) estimateBlockTime(ctx context.Context) time.Duration { - const fallbackBlockTime = time.Second - - if hm.lumeraClient == nil { - return fallbackBlockTime - } - - nodeModule := hm.lumeraClient.Node() - if nodeModule == nil { - return fallbackBlockTime - } - - latest, err := nodeModule.GetLatestBlock(ctx) - if err != nil || latest == nil || latest.Block == nil { - return fallbackBlockTime - } - - height := latest.Block.Header.Height - if height <= 1 { - return fallbackBlockTime - } - - prev, err := nodeModule.GetBlockByHeight(ctx, height-1) - if err != nil || prev == nil || prev.Block == nil { - return fallbackBlockTime - } - - delta := latest.Block.Header.Time.Sub(prev.Block.Header.Time) - if delta <= 0 { - return fallbackBlockTime - } - - return delta -} - -// Run implements the service interface for use with RunServices. -func (hm *Collector) Run(ctx context.Context) error { - // Start the collector and then block until the context is cancelled, - // at which point we gracefully stop all background work. - if err := hm.Start(ctx); err != nil { - return err - } - - // Wait for context cancellation - <-ctx.Done() - - // Gracefully stop - hm.Stop() - - return nil -} - -// Stop gracefully stops the collector. -func (hm *Collector) Stop() { - // Closing stopChan signals the reporting loop to exit and allows the - // WaitGroup to flush before returning. - close(hm.stopChan) - hm.wg.Wait() -} - -// reportingLoop runs the periodic metrics reporting. -func (hm *Collector) reportingLoop(ctx context.Context) { - defer hm.wg.Done() - - // Initial report after startup delay - startupDelay := time.Duration(DefaultStartupDelaySeconds) * time.Second - - select { - case <-time.After(startupDelay): - hm.reportHealth(ctx) - case <-hm.stopChan: - return - case <-ctx.Done(): - return - } - - // Regular reporting interval - ticker := time.NewTicker(hm.reportInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - hm.reportHealth(ctx) - case <-hm.stopChan: - return - case <-ctx.Done(): - return - } - } -} - -// reportHealth collects and reports the current metrics snapshot. -func (hm *Collector) reportHealth(ctx context.Context) { - // Collect metrics from local status/state and transform into the canonical - // SupernodeMetrics shape required by the on-chain module. - metrics, err := hm.collectMetrics(ctx) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("failed to collect health metrics: %v", err), nil) - return - } - - logtrace.Info(ctx, "Reporting supernode metrics", logtrace.Fields{ - "identity": hm.identity, - "open_ports": metrics.OpenPorts, - "uptime_secs": metrics.UptimeSeconds, - }) - - // Report the metrics snapshot to the blockchain using the supernode - // module's ReportMetrics Msg. Any failure is logged but does not panic - // or stop the reporting loop. - if err := hm.submitMetrics(ctx, metrics); err != nil { - logtrace.Error(ctx, fmt.Sprintf("failed to submit health metrics: %v", err), nil) - return - } -} - -// submitMetrics sends metrics to the blockchain. -func (hm *Collector) submitMetrics(ctx context.Context, metrics sntypes.SupernodeMetrics) error { - if hm.supernodeTx == nil { - return fmt.Errorf("supernode tx module is not initialized") - } - - identity := strings.TrimSpace(hm.identity) - if identity == "" { - return fmt.Errorf("supernode identity is not configured") - } - - resp, err := hm.supernodeTx.ReportMetrics(ctx, identity, metrics) - if err != nil { - logtrace.Error(ctx, fmt.Sprintf("failed to broadcast metrics transaction: %v", err), nil) - return err - } - - _ = resp - logtrace.Info(ctx, "Metrics transaction broadcasted", logtrace.Fields{"identity": identity}) - - return nil -} diff --git a/supernode/verifier/verifier.go b/supernode/verifier/verifier.go index 7367c089..caf53305 100644 --- a/supernode/verifier/verifier.go +++ b/supernode/verifier/verifier.go @@ -8,7 +8,6 @@ import ( "github.com/LumeraProtocol/supernode/v2/pkg/logtrace" "github.com/LumeraProtocol/supernode/v2/pkg/lumera" snmodule "github.com/LumeraProtocol/supernode/v2/pkg/lumera/modules/supernode" - adapterlumera "github.com/LumeraProtocol/supernode/v2/sdk/adapters/lumera" "github.com/LumeraProtocol/supernode/v2/supernode/config" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" @@ -130,34 +129,10 @@ func (cv *ConfigVerifier) checkSupernodeExists(ctx context.Context, result *Veri } func (cv *ConfigVerifier) checkSupernodeState(result *VerificationResult, supernodeInfo *snmodule.SuperNodeInfo) { - state := adapterlumera.ParseSupernodeState(supernodeInfo.CurrentState) - allowedStates := fmt.Sprintf("%s or %s", adapterlumera.SUPERNODE_STATE_ACTIVE, adapterlumera.SUPERNODE_STATE_POSTPONED) - - if supernodeInfo.CurrentState == "" || state == adapterlumera.SUPERNODE_STATE_ACTIVE { - return - } - - // Allow POSTPONED nodes to start, but surface this as a warning so that - // operators know the node is currently out of compliance and needs to - // recover by reporting healthy metrics. - if state == adapterlumera.SUPERNODE_STATE_POSTPONED { - result.Warnings = append(result.Warnings, ConfigError{ - Field: "state", - Expected: allowedStates, - Actual: string(state), - Message: fmt.Sprintf("Supernode state is %s; node may be out of compliance but can recover by reporting metrics", state), - }) - return + if supernodeInfo.CurrentState != "" && supernodeInfo.CurrentState != "SUPERNODE_STATE_ACTIVE" { + result.Valid = false + result.Errors = append(result.Errors, ConfigError{Field: "state", Expected: "SUPERNODE_STATE_ACTIVE", Actual: supernodeInfo.CurrentState, Message: fmt.Sprintf("Supernode state is %s (expected ACTIVE)", supernodeInfo.CurrentState)}) } - - // Any other non-empty state outside the allowed set is treated as a hard error. - result.Valid = false - result.Errors = append(result.Errors, ConfigError{ - Field: "state", - Expected: allowedStates, - Actual: supernodeInfo.CurrentState, - Message: fmt.Sprintf("Supernode state is %s (expected %s)", supernodeInfo.CurrentState, allowedStates), - }) } func (cv *ConfigVerifier) checkPortsAvailable(result *VerificationResult) { diff --git a/tests/system/e2e_cascade_test.go b/tests/system/e2e_cascade_test.go index 0f018e59..89274e33 100644 --- a/tests/system/e2e_cascade_test.go +++ b/tests/system/e2e_cascade_test.go @@ -71,8 +71,7 @@ func TestCascadeE2E(t *testing.T) { // Update the genesis file with required params before starting // - Set staking bond denom to match ulume used by gentxs // - Configure action module params used by the test - // - Relax supernode metrics params so nodes don't get POSTPONED immediately in tests - sut.ModifyGenesisJSON(t, SetStakingBondDenomUlume(t), SetActionParams(t), SetSupernodeMetricsParams(t)) + sut.ModifyGenesisJSON(t, SetStakingBondDenomUlume(t), SetActionParams(t)) // Reset and start the blockchain sut.StartChain(t) @@ -668,71 +667,3 @@ func SetStakingBondDenomUlume(t *testing.T) GenesisMutator { return state } } - -// SetSupernodeMetricsParams configures supernode metrics-related params for faster testing. -func SetSupernodeMetricsParams(t *testing.T) GenesisMutator { - return func(genesis []byte) []byte { - t.Helper() - - state, err := sjson.SetRawBytes(genesis, "app_state.supernode.params.minimum_stake_for_sn", []byte(`{"denom":"ulume","amount":"100000000"}`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.reporting_threshold", []byte(`"1"`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.slashing_threshold", []byte(`"1"`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.metrics_thresholds", []byte(`""`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.evidence_retention_period", []byte(`"180days"`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.slashing_fraction", []byte(`"0.010000000000000000"`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.inactivity_penalty_period", []byte(`"86400s"`)) - require.NoError(t, err) - - // Allow plenty of time for supernodes to start and report metrics in tests. - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.metrics_update_interval_blocks", []byte(`"120"`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.metrics_grace_period_blocks", []byte(`"120"`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.metrics_freshness_max_blocks", []byte(`"500"`)) - require.NoError(t, err) - - // Permit any version in tests; binaries are often built with "dev". - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.min_supernode_version", []byte(`"0.0.0"`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.min_cpu_cores", []byte(`1`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.max_cpu_usage_percent", []byte(`100`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.min_mem_gb", []byte(`1`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.max_mem_usage_percent", []byte(`100`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.min_storage_gb", []byte(`1`)) - require.NoError(t, err) - - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.max_storage_usage_percent", []byte(`100`)) - require.NoError(t, err) - - // Allow any open ports for multi-supernode tests. - state, err = sjson.SetRawBytes(state, "app_state.supernode.params.required_open_ports", []byte(`[]`)) - require.NoError(t, err) - - return state - } -} - -// diff --git a/tests/system/go.mod b/tests/system/go.mod index cce170ea..e220e08c 100644 --- a/tests/system/go.mod +++ b/tests/system/go.mod @@ -56,7 +56,6 @@ require ( github.com/DataDog/zstd v1.5.7 // indirect github.com/LumeraProtocol/lumera v1.8.6-rc2 // indirect github.com/LumeraProtocol/rq-go v0.2.1 // indirect - github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect diff --git a/tests/system/go.sum b/tests/system/go.sum index f75a17cf..59e6866c 100644 --- a/tests/system/go.sum +++ b/tests/system/go.sum @@ -76,8 +76,6 @@ github.com/LumeraProtocol/lumera v1.8.6-rc2 h1:o4f3HOpmpk6VU+PiFOExx6F6doffLCKJU github.com/LumeraProtocol/lumera v1.8.6-rc2/go.mod h1:DcG+PermGhl5uA51VaSA0EC+FXpDVm2XgifmYL9jJvE= github.com/LumeraProtocol/rq-go v0.2.1 h1:8B3UzRChLsGMmvZ+UVbJsJj6JZzL9P9iYxbdUwGsQI4= github.com/LumeraProtocol/rq-go v0.2.1/go.mod h1:APnKCZRh1Es2Vtrd2w4kCLgAyaL5Bqrkz/BURoRJ+O8= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=