diff --git a/doc/schemas/app_openapi.json b/doc/schemas/app_openapi.json index faf2c9cd202e..bea8614f9530 100644 --- a/doc/schemas/app_openapi.json +++ b/doc/schemas/app_openapi.json @@ -15,7 +15,8 @@ "CodeStatus": { "enum": [ "ALLOWED_TO_JOIN" - ] + ], + "type": "string" }, "EndpointMetrics__Metric": { "properties": { @@ -94,101 +95,6 @@ ], "type": "object" }, - "GetNetworkInfo__NodeInfo": { - "properties": { - "host": { - "$ref": "#/components/schemas/string" - }, - "node_id": { - "$ref": "#/components/schemas/uint64" - }, - "port": { - "$ref": "#/components/schemas/string" - } - }, - "required": [ - "node_id", - "host", - "port" - ], - "type": "object" - }, - "GetNetworkInfo__NodeInfo_array": { - "items": { - "$ref": "#/components/schemas/GetNetworkInfo__NodeInfo" - }, - "type": "array" - }, - "GetNetworkInfo__Out": { - "properties": { - "nodes": { - "$ref": "#/components/schemas/GetNetworkInfo__NodeInfo_array" - }, - "primary_id": { - "$ref": "#/components/schemas/uint64" - } - }, - "required": [ - "nodes", - "primary_id" - ], - "type": "object" - }, - "GetNodesByRPCAddress__NodeInfo": { - "properties": { - "node_id": { - "$ref": "#/components/schemas/uint64" - }, - "status": { - "$ref": "#/components/schemas/NodeStatus" - } - }, - "required": [ - "node_id", - "status" - ], - "type": "object" - }, - "GetNodesByRPCAddress__NodeInfo_array": { - "items": { - "$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo" - }, - "type": "array" - }, - "GetNodesByRPCAddress__Out": { - "properties": { - "nodes": { - "$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo_array" - } - }, - "required": [ - "nodes" - ], - "type": "object" - }, - "GetPrimaryInfo__Out": { - "properties": { - "current_view": { - "$ref": "#/components/schemas/int64" - }, - "primary_host": { - "$ref": "#/components/schemas/string" - }, - "primary_id": { - "$ref": "#/components/schemas/uint64" - }, - "primary_port": { - "$ref": "#/components/schemas/string" - } - }, - "required": [ - "primary_id", - "primary_host", - "primary_port", - "current_view" - ], - "type": "object" - }, "GetReceipt__Out": { "properties": { "receipt": { @@ -264,13 +170,6 @@ ], "type": "object" }, - "NodeStatus": { - "enum": [ - "PENDING", - "TRUSTED", - "RETIRED" - ] - }, "Report": { "properties": { "histogram": { @@ -292,7 +191,8 @@ "PENDING", "COMMITTED", "INVALID" - ] + ], + "type": "string" }, "VerifyReceipt__In": { "properties": { @@ -830,72 +730,6 @@ ] } }, - "/network_info": { - "get": { - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetNetworkInfo__Out" - } - } - }, - "description": "Default response description" - } - } - } - }, - "/node/ids": { - "get": { - "parameters": [ - { - "in": "query", - "name": "host", - "required": false, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "port", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetNodesByRPCAddress__Out" - } - } - }, - "description": "Default response description" - } - } - } - }, - "/primary_info": { - "get": { - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetPrimaryInfo__Out" - } - } - }, - "description": "Default response description" - } - } - } - }, "/receipt": { "get": { "parameters": [ diff --git a/doc/schemas/gov_openapi.json b/doc/schemas/gov_openapi.json index 94a99ac16e26..9a645fddeb39 100644 --- a/doc/schemas/gov_openapi.json +++ b/doc/schemas/gov_openapi.json @@ -15,7 +15,8 @@ "CodeStatus": { "enum": [ "ALLOWED_TO_JOIN" - ] + ], + "type": "string" }, "EndpointMetrics__Metric": { "properties": { @@ -94,101 +95,6 @@ ], "type": "object" }, - "GetNetworkInfo__NodeInfo": { - "properties": { - "host": { - "$ref": "#/components/schemas/string" - }, - "node_id": { - "$ref": "#/components/schemas/uint64" - }, - "port": { - "$ref": "#/components/schemas/string" - } - }, - "required": [ - "node_id", - "host", - "port" - ], - "type": "object" - }, - "GetNetworkInfo__NodeInfo_array": { - "items": { - "$ref": "#/components/schemas/GetNetworkInfo__NodeInfo" - }, - "type": "array" - }, - "GetNetworkInfo__Out": { - "properties": { - "nodes": { - "$ref": "#/components/schemas/GetNetworkInfo__NodeInfo_array" - }, - "primary_id": { - "$ref": "#/components/schemas/uint64" - } - }, - "required": [ - "nodes", - "primary_id" - ], - "type": "object" - }, - "GetNodesByRPCAddress__NodeInfo": { - "properties": { - "node_id": { - "$ref": "#/components/schemas/uint64" - }, - "status": { - "$ref": "#/components/schemas/NodeStatus" - } - }, - "required": [ - "node_id", - "status" - ], - "type": "object" - }, - "GetNodesByRPCAddress__NodeInfo_array": { - "items": { - "$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo" - }, - "type": "array" - }, - "GetNodesByRPCAddress__Out": { - "properties": { - "nodes": { - "$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo_array" - } - }, - "required": [ - "nodes" - ], - "type": "object" - }, - "GetPrimaryInfo__Out": { - "properties": { - "current_view": { - "$ref": "#/components/schemas/int64" - }, - "primary_host": { - "$ref": "#/components/schemas/string" - }, - "primary_id": { - "$ref": "#/components/schemas/uint64" - }, - "primary_port": { - "$ref": "#/components/schemas/string" - } - }, - "required": [ - "primary_id", - "primary_host", - "primary_port", - "current_view" - ], - "type": "object" - }, "GetReceipt__Out": { "properties": { "receipt": { @@ -226,13 +132,6 @@ ], "type": "object" }, - "NodeStatus": { - "enum": [ - "PENDING", - "TRUSTED", - "RETIRED" - ] - }, "Proposal": { "properties": { "parameter": { @@ -286,7 +185,8 @@ "WITHDRAWN", "REJECTED", "FAILED" - ] + ], + "type": "string" }, "Propose__In": { "properties": { @@ -330,7 +230,8 @@ "PENDING", "COMMITTED", "INVALID" - ] + ], + "type": "string" }, "VerifyReceipt__In": { "properties": { @@ -598,72 +499,6 @@ } } }, - "/network_info": { - "get": { - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetNetworkInfo__Out" - } - } - }, - "description": "Default response description" - } - } - } - }, - "/node/ids": { - "get": { - "parameters": [ - { - "in": "query", - "name": "host", - "required": false, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "port", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetNodesByRPCAddress__Out" - } - } - }, - "description": "Default response description" - } - } - } - }, - "/primary_info": { - "get": { - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetPrimaryInfo__Out" - } - } - }, - "description": "Default response description" - } - } - } - }, "/proposals": { "post": { "requestBody": { diff --git a/doc/schemas/node_openapi.json b/doc/schemas/node_openapi.json index d7b8923e99a1..e011b61f5c9e 100644 --- a/doc/schemas/node_openapi.json +++ b/doc/schemas/node_openapi.json @@ -15,7 +15,8 @@ "CodeStatus": { "enum": [ "ALLOWED_TO_JOIN" - ] + ], + "type": "string" }, "EndpointMetrics__Metric": { "properties": { @@ -94,71 +95,70 @@ ], "type": "object" }, - "GetNetworkInfo__NodeInfo": { - "properties": { - "host": { - "$ref": "#/components/schemas/string" - }, - "node_id": { - "$ref": "#/components/schemas/uint64" - }, - "port": { - "$ref": "#/components/schemas/string" - } - }, - "required": [ - "node_id", - "host", - "port" - ], - "type": "object" - }, - "GetNetworkInfo__NodeInfo_array": { - "items": { - "$ref": "#/components/schemas/GetNetworkInfo__NodeInfo" - }, - "type": "array" - }, "GetNetworkInfo__Out": { "properties": { - "nodes": { - "$ref": "#/components/schemas/GetNetworkInfo__NodeInfo_array" + "current_view": { + "$ref": "#/components/schemas/int64" }, "primary_id": { "$ref": "#/components/schemas/uint64" + }, + "service_status": { + "$ref": "#/components/schemas/ServiceStatus" } }, "required": [ - "nodes", + "service_status", + "current_view", "primary_id" ], "type": "object" }, - "GetNodesByRPCAddress__NodeInfo": { + "GetNode__NodeInfo": { "properties": { + "host": { + "$ref": "#/components/schemas/string" + }, + "local_host": { + "$ref": "#/components/schemas/string" + }, + "local_port": { + "$ref": "#/components/schemas/string" + }, "node_id": { "$ref": "#/components/schemas/uint64" }, + "port": { + "$ref": "#/components/schemas/string" + }, + "primary": { + "$ref": "#/components/schemas/boolean" + }, "status": { "$ref": "#/components/schemas/NodeStatus" } }, "required": [ "node_id", - "status" + "status", + "host", + "port", + "local_host", + "local_port", + "primary" ], "type": "object" }, - "GetNodesByRPCAddress__NodeInfo_array": { + "GetNode__NodeInfo_array": { "items": { - "$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo" + "$ref": "#/components/schemas/GetNode__NodeInfo" }, "type": "array" }, - "GetNodesByRPCAddress__Out": { + "GetNodes__Out": { "properties": { "nodes": { - "$ref": "#/components/schemas/GetNodesByRPCAddress__NodeInfo_array" + "$ref": "#/components/schemas/GetNode__NodeInfo_array" } }, "required": [ @@ -166,29 +166,6 @@ ], "type": "object" }, - "GetPrimaryInfo__Out": { - "properties": { - "current_view": { - "$ref": "#/components/schemas/int64" - }, - "primary_host": { - "$ref": "#/components/schemas/string" - }, - "primary_id": { - "$ref": "#/components/schemas/uint64" - }, - "primary_port": { - "$ref": "#/components/schemas/string" - } - }, - "required": [ - "primary_id", - "primary_host", - "primary_port", - "current_view" - ], - "type": "object" - }, "GetQuotes__Out": { "properties": { "quotes": { @@ -298,7 +275,17 @@ "PENDING", "TRUSTED", "RETIRED" - ] + ], + "type": "string" + }, + "ServiceStatus": { + "enum": [ + "OPENING", + "OPEN", + "WAITING_FOR_RECOVERY_SHARES", + "CLOSED" + ], + "type": "string" }, "TxStatus": { "enum": [ @@ -306,7 +293,8 @@ "PENDING", "COMMITTED", "INVALID" - ] + ], + "type": "string" }, "VerifyReceipt__In": { "properties": { @@ -343,7 +331,8 @@ "readingPublicLedger", "readingPrivateLedger", "verifyingSnapshot" - ] + ], + "type": "string" }, "int64": { "maximum": 9223372036854775807, @@ -531,15 +520,6 @@ } }, "/network": { - "get": { - "responses": { - "200": { - "description": "Default response description" - } - } - } - }, - "/network_info": { "get": { "responses": { "200": { @@ -555,7 +535,7 @@ } } }, - "/node/ids": { + "/network/nodes": { "get": { "parameters": [ { @@ -573,6 +553,19 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "status", + "required": false, + "schema": { + "enum": [ + "PENDING", + "TRUSTED", + "RETIRED" + ], + "type": "string" + } } ], "responses": { @@ -580,7 +573,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetNodesByRPCAddress__Out" + "$ref": "#/components/schemas/GetNodes__Out" } } }, @@ -589,8 +582,8 @@ } } }, - "/primary": { - "head": { + "/network/nodes/primary": { + "get": { "responses": { "200": { "description": "Default response description" @@ -598,20 +591,48 @@ } } }, - "/primary_info": { + "/network/nodes/self": { + "get": { + "responses": { + "200": { + "description": "Default response description" + } + } + } + }, + "/network/nodes/{node_id}": { "get": { "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetPrimaryInfo__Out" + "$ref": "#/components/schemas/GetNode__NodeInfo" } } }, "description": "Default response description" } } + }, + "parameters": [ + { + "in": "path", + "name": "node_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/primary": { + "head": { + "responses": { + "200": { + "description": "Default response description" + } + } } }, "/quote": { diff --git a/src/ds/json.h b/src/ds/json.h index 5a845762c78c..1ba95514956a 100644 --- a/src/ds/json.h +++ b/src/ds/json.h @@ -736,6 +736,7 @@ namespace std enums.push_back(p.second); \ } \ j["enum"] = enums; \ + j["type"] = "string"; \ } #pragma clang diagnostic pop \ No newline at end of file diff --git a/src/node/rpc/call_types.h b/src/node/rpc/call_types.h index 54a90a72a8f4..bba1eb7c1a70 100644 --- a/src/node/rpc/call_types.h +++ b/src/node/rpc/call_types.h @@ -8,6 +8,7 @@ #include "node/identity.h" #include "node/ledger_secrets.h" #include "node/nodes.h" +#include "node/service.h" #include "node_call_types.h" #include "tx_status.h" @@ -38,17 +39,6 @@ namespace ccf }; }; - struct GetPrimaryInfo - { - struct Out - { - NodeId primary_id; - std::string primary_host; - std::string primary_port; - kv::Consensus::View current_view; - }; - }; - struct GetCode { struct Version @@ -65,38 +55,42 @@ namespace ccf struct GetNetworkInfo { - struct NodeInfo - { - NodeId node_id; - std::string host; - std::string port; - }; - struct Out { - std::vector nodes = {}; - std::optional primary_id = std::nullopt; + ServiceStatus service_status; + std::optional current_view; + std::optional primary_id; }; }; - struct GetNodesByRPCAddress + struct GetNode { struct NodeInfo { NodeId node_id; NodeStatus status; + std::string host; + std::string port; + std::string local_host; + std::string local_port; + bool primary; }; + using Out = NodeInfo; + }; + + struct GetNodes + { struct In { - std::string host; - std::string port; - bool retired = false; + std::optional host; + std::optional port; + std::optional status; }; struct Out { - std::vector nodes = {}; + std::vector nodes = {}; }; }; diff --git a/src/node/rpc/common_endpoint_registry.h b/src/node/rpc/common_endpoint_registry.h index a3ea738562e1..8491e66ebcda 100644 --- a/src/node/rpc/common_endpoint_registry.h +++ b/src/node/rpc/common_endpoint_registry.h @@ -142,67 +142,6 @@ namespace ccf .set_auto_schema() .install(); - auto get_primary_info = [this](auto& args, nlohmann::json&&) { - if (consensus != nullptr) - { - NodeId primary_id = consensus->primary(); - auto current_view = consensus->get_view(); - - auto nodes_view = - args.tx.template get_read_only_view(Tables::NODES); - auto info = nodes_view->get(primary_id); - - if (info) - { - GetPrimaryInfo::Out out; - out.primary_id = primary_id; - out.primary_host = info->pubhost; - out.primary_port = info->pubport; - out.current_view = current_view; - return make_success(out); - } - } - - return make_error( - HTTP_STATUS_INTERNAL_SERVER_ERROR, - ccf::errors::InternalError, - "Primary unknown."); - }; - make_read_only_endpoint( - "primary_info", - HTTP_GET, - json_read_only_adapter(get_primary_info), - no_auth_required) - .set_auto_schema() - .install(); - - auto get_network_info = [this](auto& args, nlohmann::json&&) { - GetNetworkInfo::Out out; - if (consensus != nullptr) - { - out.primary_id = consensus->primary(); - } - - auto nodes_view = - args.tx.template get_read_only_view(Tables::NODES); - nodes_view->foreach([&out](const NodeId& nid, const NodeInfo& ni) { - if (ni.status == ccf::NodeStatus::TRUSTED) - { - out.nodes.push_back({nid, ni.pubhost, ni.pubport}); - } - return true; - }); - - return make_success(out); - }; - make_read_only_endpoint( - "network_info", - HTTP_GET, - json_read_only_adapter(get_network_info), - no_auth_required) - .set_auto_schema() - .install(); - auto get_code = [](auto& args, nlohmann::json&&) { GetCode::Out out; @@ -222,33 +161,6 @@ namespace ccf .set_auto_schema() .install(); - auto get_nodes_by_rpc_address = [](auto& args, nlohmann::json&& params) { - const auto in = params.get(); - - GetNodesByRPCAddress::Out out; - auto nodes_view = - args.tx.template get_read_only_view(Tables::NODES); - nodes_view->foreach([&in, &out](const NodeId& nid, const NodeInfo& ni) { - if (ni.pubhost == in.host && ni.pubport == in.port) - { - if (ni.status != ccf::NodeStatus::RETIRED || in.retired) - { - out.nodes.push_back({nid, ni.status}); - } - } - return true; - }); - - return make_success(out); - }; - make_read_only_endpoint( - "node/ids", - HTTP_GET, - json_read_only_adapter(get_nodes_by_rpc_address), - no_auth_required) - .set_auto_schema() - .install(); - auto openapi = [this](kv::Tx& tx, nlohmann::json&&) { auto document = ds::openapi::create_document( openapi_info.title, diff --git a/src/node/rpc/endpoint_registry.h b/src/node/rpc/endpoint_registry.h index e41cdcb03673..dddd83599b54 100644 --- a/src/node/rpc/endpoint_registry.h +++ b/src/node/rpc/endpoint_registry.h @@ -17,6 +17,7 @@ #include "node/certs.h" #include "serialization.h" +#include #include #include #include @@ -468,6 +469,33 @@ namespace ccf return spec; } + template + bool get_path_param( + const enclave::PathParams& params, + const std::string& param_name, + T& value, + std::string& error) + { + const auto it = params.find(param_name); + if (it == params.end()) + { + error = fmt::format("No parameter named '{}' in path", param_name); + return false; + } + + const auto param_s = it->second; + const auto [p, ec] = + std::from_chars(param_s.data(), param_s.data() + param_s.size(), value); + if (ec != std::errc()) + { + error = fmt::format( + "Unable to parse path parameter '{}' as a {}", param_s, param_name); + return false; + } + + return true; + } + protected: EndpointPtr default_endpoint; std::map> @@ -497,7 +525,8 @@ namespace ccf fmt::format("Unexpected params schema type: {}", schema.dump())); } - const auto& required_parameters = schema["required"]; + const auto& required_parameters = + schema.value("required", nlohmann::json::array()); for (const auto& [name, schema] : schema["properties"].items()) { auto parameter = nlohmann::json::object(); diff --git a/src/node/rpc/member_frontend.h b/src/node/rpc/member_frontend.h index f6676564a463..afcf1edad0b8 100644 --- a/src/node/rpc/member_frontend.h +++ b/src/node/rpc/member_frontend.h @@ -1155,33 +1155,6 @@ namespace ccf return ProposalInfo{proposal_id, proposal.proposer, proposal.state}; } - template - bool get_path_param( - const enclave::PathParams& params, - const std::string& param_name, - T& value, - std::string& error) - { - const auto it = params.find(param_name); - if (it == params.end()) - { - error = fmt::format("No parameter named '{}' in path", param_name); - return false; - } - - const auto param_s = it->second; - const auto [p, ec] = - std::from_chars(param_s.data(), param_s.data() + param_s.size(), value); - if (ec != std::errc()) - { - error = fmt::format( - "Unable to parse path parameter '{}' as a {}", param_s, param_name); - return false; - } - - return true; - } - bool get_proposal_id_from_path( const enclave::PathParams& params, ObjectId& proposal_id, diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index bb0519ac625b..810e9412d0f7 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -329,22 +329,180 @@ namespace ccf .install(); auto network_status = [this](auto& args, nlohmann::json&&) { + GetNetworkInfo::Out out; auto service_view = args.tx.get_read_only_view(network.service); auto service_state = service_view->get(0); if (service_state.has_value()) { - return make_success(service_state.value().status); + out.service_status = service_state.value().status; + if (consensus != nullptr) + { + out.current_view = consensus->get_view(); + + auto primary_id = consensus->primary(); + auto nodes_view = args.tx.get_read_only_view(this->network.nodes); + auto info = nodes_view->get(primary_id); + if (info) + { + out.primary_id = primary_id; + } + } + return make_success(out); } return make_error( HTTP_STATUS_NOT_FOUND, ccf::errors::ResourceNotFound, - "Network status is unknown."); + "Service state not available."); }; make_read_only_endpoint( "network", HTTP_GET, json_read_only_adapter(network_status), no_auth_required) + .set_auto_schema() + .install(); + + auto get_nodes = [this](auto& args, nlohmann::json&& params) { + const auto in = params.get(); + GetNodes::Out out; + + auto nodes_view = args.tx.get_read_only_view(this->network.nodes); + nodes_view->foreach( + [this, &in, &out](const NodeId& nid, const NodeInfo& ni) { + if (in.host.has_value() && in.host.value() != ni.pubhost) + return true; + if (in.port.has_value() && in.port.value() != ni.pubport) + return true; + if (in.status.has_value() && in.status.value() != ni.status) + return true; + bool is_primary = false; + if (consensus != nullptr) + { + is_primary = consensus->primary() == nid; + } + out.nodes.push_back({nid, + ni.status, + ni.pubhost, + ni.pubport, + ni.rpchost, + ni.rpcport, + is_primary}); + return true; + }); + + return make_success(out); + }; + make_read_only_endpoint( + "network/nodes", + HTTP_GET, + json_read_only_adapter(get_nodes), + no_auth_required) + .set_auto_schema() + .install(); + + auto get_node_info = [this](auto& args, nlohmann::json&&) { + NodeId node_id; + std::string error; + if (!get_path_param( + args.rpc_ctx->get_request_path_params(), + "node_id", + node_id, + error)) + { + return make_error( + HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName, error); + } + + auto nodes_view = args.tx.get_read_only_view(this->network.nodes); + auto info = nodes_view->get(node_id); + + if (!info) + { + return make_error( + HTTP_STATUS_NOT_FOUND, + ccf::errors::ResourceNotFound, + "Node not found"); + } + + bool is_primary = false; + if (consensus != nullptr) + { + is_primary = consensus->primary() == node_id; + } + auto ni = info.value(); + return make_success({node_id, + ni.status, + ni.pubhost, + ni.pubport, + ni.rpchost, + ni.rpcport, + is_primary}); + }; + make_read_only_endpoint( + "network/nodes/{node_id}", + HTTP_GET, + json_read_only_adapter(get_node_info), + no_auth_required) + .set_auto_schema() + .install(); + + auto get_self_node = [this](ReadOnlyEndpointContext& args) { + auto node_id = this->node.get_node_id(); + auto nodes_view = args.tx.get_read_only_view(this->network.nodes); + auto info = nodes_view->get(node_id); + if (info) + { + args.rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT); + args.rpc_ctx->set_response_header( + "Location", + fmt::format( + "https://{}:{}/node/network/nodes/{}", + info->pubhost, + info->pubport, + node_id)); + return; + } + + args.rpc_ctx->set_error( + HTTP_STATUS_INTERNAL_SERVER_ERROR, + ccf::errors::InternalError, + "Node info not available"); + }; + make_read_only_endpoint( + "network/nodes/self", HTTP_GET, get_self_node, no_auth_required) + .set_forwarding_required(ForwardingRequired::Never) + .install(); + + auto get_primary_node = [this](ReadOnlyEndpointContext& args) { + if (consensus != nullptr) + { + auto node_id = this->node.get_node_id(); + auto primary_id = consensus->primary(); + auto nodes_view = args.tx.get_read_only_view(this->network.nodes); + auto info = nodes_view->get(node_id); + auto info_primary = nodes_view->get(primary_id); + if (info && info_primary) + { + args.rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT); + args.rpc_ctx->set_response_header( + "Location", + fmt::format( + "https://{}:{}/node/network/nodes/{}", + info->pubhost, + info->pubport, + node_id)); + return; + } + } + + args.rpc_ctx->set_error( + HTTP_STATUS_INTERNAL_SERVER_ERROR, + ccf::errors::InternalError, + "Primary unknown"); + }; + make_read_only_endpoint( + "network/nodes/primary", HTTP_GET, get_primary_node, no_auth_required) + .set_forwarding_required(ForwardingRequired::Never) .install(); auto is_primary = [this](ReadOnlyEndpointContext& args) { diff --git a/src/node/rpc/serialization.h b/src/node/rpc/serialization.h index bbdb17257d99..98078715b89f 100644 --- a/src/node/rpc/serialization.h +++ b/src/node/rpc/serialization.h @@ -82,21 +82,33 @@ namespace ccf DECLARE_JSON_TYPE(GetTxStatus::Out) DECLARE_JSON_REQUIRED_FIELDS(GetTxStatus::Out, status) - DECLARE_JSON_TYPE(GetPrimaryInfo::Out) + DECLARE_JSON_TYPE(GetNetworkInfo::Out) DECLARE_JSON_REQUIRED_FIELDS( - GetPrimaryInfo::Out, primary_id, primary_host, primary_port, current_view) + GetNetworkInfo::Out, service_status, current_view, primary_id) - DECLARE_JSON_TYPE(GetNetworkInfo::NodeInfo) - DECLARE_JSON_REQUIRED_FIELDS(GetNetworkInfo::NodeInfo, node_id, host, port) - DECLARE_JSON_TYPE(GetNetworkInfo::Out) - DECLARE_JSON_REQUIRED_FIELDS(GetNetworkInfo::Out, nodes, primary_id) - - DECLARE_JSON_TYPE(GetNodesByRPCAddress::In) - DECLARE_JSON_REQUIRED_FIELDS(GetNodesByRPCAddress::In, host, port) - DECLARE_JSON_TYPE(GetNodesByRPCAddress::NodeInfo) - DECLARE_JSON_REQUIRED_FIELDS(GetNodesByRPCAddress::NodeInfo, node_id, status) - DECLARE_JSON_TYPE(GetNodesByRPCAddress::Out) - DECLARE_JSON_REQUIRED_FIELDS(GetNodesByRPCAddress::Out, nodes) + DECLARE_JSON_TYPE(GetNode::NodeInfo) + DECLARE_JSON_REQUIRED_FIELDS( + GetNode::NodeInfo, + node_id, + status, + host, + port, + local_host, + local_port, + primary) + + DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(GetNodes::In) + // Current limitation of the JSON macros: It is necessary to defined + // DECLARE_JSON_REQUIRED_FIELDS even though there are no required + // fields. This raises some compiler warnings that are disabled locally. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" + DECLARE_JSON_REQUIRED_FIELDS(GetNodes::In); +#pragma clang diagnostic pop + DECLARE_JSON_OPTIONAL_FIELDS(GetNodes::In, host, port, status) + DECLARE_JSON_TYPE(GetNodes::Out) + DECLARE_JSON_REQUIRED_FIELDS(GetNodes::Out, nodes) DECLARE_JSON_TYPE(CallerInfo) DECLARE_JSON_REQUIRED_FIELDS(CallerInfo, caller_id) diff --git a/tests/governance.py b/tests/governance.py index a1c734f6e121..8c99d65bbcc1 100644 --- a/tests/governance.py +++ b/tests/governance.py @@ -171,16 +171,19 @@ def test_user_id(network, args): return network -@reqs.description("Check node/ids endpoint") +@reqs.description("Check network/nodes endpoint") def test_node_ids(network, args): nodes = network.find_nodes() for node in nodes: with node.client() as c: - r = c.get(f'/node/node/ids?host="{node.pubhost}"&port="{node.pubport}"') + r = c.get( + f'/node/network/nodes?host="{node.pubhost}"&port="{node.pubport}"' + ) assert r.status_code == 200 - assert r.body.json()["nodes"] == [ - {"node_id": node.node_id, "status": "TRUSTED"} - ] + info = r.body.json()["nodes"] + assert len(info) == 1 + assert info[0]["node_id"] == node.node_id + assert info[0]["status"] == "TRUSTED" return network diff --git a/tests/infra/network.py b/tests/infra/network.py index 4390d6fa2def..f8712a6d22ae 100644 --- a/tests/infra/network.py +++ b/tests/infra/network.py @@ -147,10 +147,10 @@ def _adjust_local_node_ids(self, primary): ), "Cannot adjust local node IDs if the network was started from an existing network" with primary.client() as nc: - r = nc.get("/node/primary_info") - first_node_id = r.body.json()["primary_id"] - assert (r.body.json()["primary_host"] == primary.pubhost) and ( - int(r.body.json()["primary_port"]) == primary.pubport + r = nc.get("/node/network/nodes/primary") + first_node_id = r.body.json()["node_id"] + assert (r.body.json()["host"] == primary.pubhost) and ( + int(r.body.json()["port"]) == primary.pubport ), "Primary is not the node that just started" for n in self.nodes: n.node_id = n.node_id + first_node_id @@ -660,14 +660,14 @@ def find_primary(self, timeout=3, log_capture=None): with node.client() as c: try: logs = [] - res = c.get("/node/primary_info", log_capture=logs) - if res.status_code == 200: - body = res.body.json() - primary_id = body["primary_id"] - view = body["current_view"] + res = c.get("/node/network", log_capture=logs) + assert res.status_code == 200, res + body = res.body.json() + primary_id = body["primary_id"] + view = body["current_view"] + if primary_id is not None: break - else: - assert "Primary unknown" in res.body.text(), res + except CCFConnectionException: LOG.warning( f"Could not successfully connect to node {node.node_id}. Retrying..." diff --git a/tests/rekey.py b/tests/rekey.py index 82c2d464c598..d30fad670a0d 100644 --- a/tests/rekey.py +++ b/tests/rekey.py @@ -7,7 +7,6 @@ @reqs.description("Rekey the ledger once") -@reqs.supports_methods("primary_info") @reqs.at_least_n_nodes(1) def test(network, args): primary, _ = network.find_primary()