415 auto accept = [
this](
auto& args,
const nlohmann::json& params) {
424 HTTP_STATUS_INTERNAL_SERVER_ERROR,
425 ccf::errors::InternalError,
426 "Target node should be part of network to accept new nodes.");
432 auto this_startup_seqno =
435 in.startup_seqno.has_value() &&
436 this_startup_seqno > in.startup_seqno.value())
439 HTTP_STATUS_BAD_REQUEST,
440 ccf::errors::StartupSeqnoIsOld,
442 "Node requested to join from seqno {} which is older than this "
443 "node startup seqno {}. A snapshot at least as recent as {} must "
445 in.startup_seqno.value(),
447 this_startup_seqno));
451 auto service = args.tx.rw(this->network.
service);
453 auto active_service = service->get();
454 if (!active_service.has_value())
457 HTTP_STATUS_INTERNAL_SERVER_ERROR,
458 ccf::errors::InternalError,
459 "No service is available to accept new node.");
462 auto config = args.tx.ro(network.
config);
463 auto service_config = config->get();
473 auto existing_node_info = check_node_exists(
475 args.rpc_ctx->get_session_context()->caller_cert,
476 joining_node_status);
477 if (existing_node_info.has_value())
485 this->network.ledger_secrets->get(
486 args.tx, existing_node_info->ledger_secret_seqno),
487 *this->network.identity.get(),
488 active_service->status,
489 existing_node_info->endorsed_certificate,
498 if (primary_id.has_value())
500 auto info =
nodes->get(primary_id.value());
504 args.rpc_ctx->get_session_context()->interface_id;
505 if (!interface_id.has_value())
508 HTTP_STATUS_INTERNAL_SERVER_ERROR,
509 ccf::errors::InternalError,
510 "Cannot redirect non-RPC request.");
512 const auto& address =
513 info->rpc_interfaces[interface_id.value()].published_address;
514 args.rpc_ctx->set_response_header(
515 http::headers::LOCATION,
516 fmt::format(
"https://{}/node/join", address));
519 HTTP_STATUS_PERMANENT_REDIRECT,
520 ccf::errors::NodeCannotHandleRequest,
521 "Node is not primary; cannot handle write");
526 HTTP_STATUS_INTERNAL_SERVER_ERROR,
527 ccf::errors::InternalError,
533 args.rpc_ctx->get_session_context()->caller_cert,
536 active_service->status,
545 auto existing_node_info = check_node_exists(
546 args.tx, args.rpc_ctx->get_session_context()->caller_cert);
547 if (existing_node_info.has_value())
553 auto node_info =
nodes->get(existing_node_info->node_id);
554 auto node_status = node_info->status;
556 if (is_taking_part_in_acking(node_status))
562 this->network.ledger_secrets->get(
563 args.tx, existing_node_info->ledger_secret_seqno),
564 *this->network.identity.get(),
565 active_service->status,
566 existing_node_info->endorsed_certificate,
579 HTTP_STATUS_BAD_REQUEST,
580 ccf::errors::InvalidNodeState,
582 "Joining node is not in expected state ({}).", node_status));
590 if (primary_id.has_value())
592 auto info =
nodes->get(primary_id.value());
596 args.rpc_ctx->get_session_context()->interface_id;
597 if (!interface_id.has_value())
600 HTTP_STATUS_INTERNAL_SERVER_ERROR,
601 ccf::errors::InternalError,
602 "Cannot redirect non-RPC request.");
604 const auto& address =
605 info->rpc_interfaces[interface_id.value()].published_address;
606 args.rpc_ctx->set_response_header(
607 http::headers::LOCATION,
608 fmt::format(
"https://{}/node/join", address));
611 HTTP_STATUS_PERMANENT_REDIRECT,
612 ccf::errors::NodeCannotHandleRequest,
613 "Node is not primary; cannot handle write");
618 HTTP_STATUS_INTERNAL_SERVER_ERROR,
619 ccf::errors::InternalError,
626 args.rpc_ctx->get_session_context()->caller_cert,
629 active_service->status,
633 make_endpoint(
"/join", HTTP_POST,
json_adapter(accept), no_auth_required)
635 .set_openapi_hidden(
true)
638 auto set_retired_committed = [
this](
auto& ctx, nlohmann::json&&) {
640 nodes->foreach([
this, &
nodes](
const auto& node_id,
auto node_info) {
641 auto gc_node =
nodes->get_globally_committed(node_id);
643 gc_node.has_value() &&
645 !node_info.retired_committed)
649 node_info.retired_committed =
true;
650 nodes->put(node_id, node_info);
652 LOG_DEBUG_FMT(
"Setting retired_committed on node {}", node_id);
660 "network/nodes/set_retired_committed",
663 {std::make_shared<NodeCertAuthnPolicy>()})
664 .set_openapi_hidden(
true)
667 auto get_state = [
this](
auto& args, nlohmann::json&&) {
669 auto [s, rts, lrs] = this->node_operation.
state();
670 result.
node_id = this->context.get_node_id();
677 auto signatures = args.tx.template ro<Signatures>(Tables::SIGNATURES);
678 auto sig = signatures->get();
679 if (!sig.has_value())
688 auto node_configuration_subsystem =
690 if (!node_configuration_subsystem)
693 HTTP_STATUS_INTERNAL_SERVER_ERROR,
694 ccf::errors::InternalError,
695 "NodeConfigurationSubsystem is not available");
698 node_configuration_subsystem->has_received_stop_notice();
702 make_read_only_endpoint(
708 auto get_quote = [
this](
auto& args, nlohmann::json&&) {
711 get_quote_for_this_node_v1(args.tx, node_quote_info);
715 q.
node_id = context.get_node_id();
728 auto node_info =
nodes->get(context.get_node_id());
729 if (node_info.has_value() && node_info->code_digest.has_value())
731 q.
mrenclave = node_info->code_digest.value();
737 if (measurement.has_value())
739 q.
mrenclave = measurement.value().hex_str();
744 HTTP_STATUS_INTERNAL_SERVER_ERROR,
745 ccf::errors::InvalidQuote,
746 "Failed to extract code id from node quote.");
755 HTTP_STATUS_NOT_FOUND,
756 ccf::errors::ResourceNotFound,
757 "Could not find node quote.");
762 HTTP_STATUS_INTERNAL_SERVER_ERROR,
763 ccf::errors::InternalError,
767 make_read_only_endpoint(
772 .set_auto_schema<void,
Quote>()
776 auto get_quotes = [
this](
auto& args, nlohmann::json&&) {
781 const auto& node_id,
const auto& node_info) {
786 q.
raw = node_info.quote_info.quote;
788 q.
format = node_info.quote_info.format;
796 if (node_info.code_digest.has_value())
798 q.
mrenclave = node_info.code_digest.value();
804 if (measurement.has_value())
806 q.
mrenclave = measurement.value().hex_str();
809 quotes.emplace_back(q);
816 make_read_only_endpoint(
824 auto network_status = [
this](
auto& args, nlohmann::json&&) {
826 auto service = args.tx.ro(network.
service);
827 auto service_state = service->get();
828 if (service_state.has_value())
830 const auto& service_value = service_state.value();
836 service_value.current_service_create_txid;
841 if (primary_id.has_value())
849 HTTP_STATUS_NOT_FOUND,
850 ccf::errors::ResourceNotFound,
851 "Service state not available.");
853 make_read_only_endpoint(
861 auto service_previous_identity = [
this](
auto& args, nlohmann::json&&) {
862 auto psi_handle = args.tx.template ro<ccf::PreviousServiceIdentity>(
863 ccf::Tables::PREVIOUS_SERVICE_IDENTITY);
864 const auto psi = psi_handle->get();
874 HTTP_STATUS_NOT_FOUND,
875 ccf::errors::ResourceNotFound,
876 "This service is not a recovery of a previous service.");
879 make_read_only_endpoint(
880 "/service/previous_identity",
887 auto get_nodes = [
this](
auto& args, nlohmann::json&&) {
888 const auto parsed_query =
889 http::parse_query(args.rpc_ctx->get_request_query());
892 const auto host = http::get_query_value_opt<std::string>(
894 const auto port = http::get_query_value_opt<std::string>(
896 const auto status_str = http::get_query_value_opt<std::string>(
899 std::optional<NodeStatus> status;
900 if (status_str.has_value())
906 status = nlohmann::json(status_str.value()).get<
NodeStatus>();
911 HTTP_STATUS_BAD_REQUEST,
912 ccf::errors::InvalidQueryParameterValue,
914 "Query parameter '{}' is not a valid node status",
915 status_str.value()));
924 if (status.has_value() && status.value() != ni.
status)
930 bool is_matched =
false;
933 const auto& [pub_host, pub_port] =
934 split_net_address(interface.second.published_address);
937 (!
host.has_value() ||
host.value() == pub_host) &&
938 (!port.has_value() || port.value() == pub_port))
950 bool is_primary =
false;
953 is_primary =
consensus->primary() == nid;
962 nodes->get_version_of_previous_write(nid).value_or(0)});
968 make_read_only_endpoint(
974 .add_query_parameter<std::string>(
976 .add_query_parameter<std::string>(
978 .add_query_parameter<std::string>(
982 auto get_removable_nodes = [
this](
auto& args, nlohmann::json&&) {
994 auto node =
nodes->get_globally_committed(node_id);
997 node->retired_committed)
1003 node->rpc_interfaces,
1005 nodes->get_version_of_previous_write(node_id).value_or(0)});
1013 make_read_only_endpoint(
1014 "/network/removable_nodes",
1021 auto delete_retired_committed_node =
1022 [
this](
auto& args, nlohmann::json&&) {
1025 std::string node_id;
1027 if (!get_path_param(
1028 args.rpc_ctx->get_request_path_params(),
1034 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName,
error);
1037 auto nodes = args.tx.rw(this->network.
nodes);
1038 if (!
nodes->has(node_id))
1041 HTTP_STATUS_NOT_FOUND,
1042 ccf::errors::ResourceNotFound,
1046 auto node_endorsed_certificates =
1060 auto node =
nodes->get_globally_committed(node_id);
1063 node->retired_committed)
1065 nodes->remove(node_id);
1066 node_endorsed_certificates->remove(node_id);
1071 HTTP_STATUS_BAD_REQUEST,
1072 ccf::errors::NodeNotRetiredCommitted,
1073 "Node is not completely retired");
1080 "/network/nodes/{node_id}",
1084 .set_auto_schema<void,
bool>()
1087 auto get_self_signed_certificate = [
this](
auto& args, nlohmann::json&&) {
1091 make_command_endpoint(
1092 "/self_signed_certificate",
1100 auto get_node_info = [
this](
auto& args, nlohmann::json&&) {
1101 std::string node_id;
1103 if (!get_path_param(
1104 args.rpc_ctx->get_request_path_params(),
1110 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName,
error);
1113 auto nodes = args.tx.ro(this->network.
nodes);
1114 auto info =
nodes->get(node_id);
1119 HTTP_STATUS_NOT_FOUND,
1120 ccf::errors::ResourceNotFound,
1124 bool is_primary =
false;
1128 if (primary.has_value() && primary.value() == node_id)
1133 auto& ni = info.value();
1140 nodes->get_version_of_previous_write(node_id).value_or(0)});
1142 make_read_only_endpoint(
1143 "/network/nodes/{node_id}",
1150 auto get_self_node = [
this](
auto& args, nlohmann::json&&) {
1151 auto node_id = this->context.get_node_id();
1152 auto nodes = args.tx.ro(this->network.
nodes);
1153 auto info =
nodes->get(node_id);
1155 bool is_primary =
false;
1159 if (primary.has_value() && primary.value() == node_id)
1165 if (info.has_value())
1169 auto& ni = info.value();
1176 nodes->get_version_of_previous_write(node_id).value_or(0)});
1181 auto node_configuration_subsystem =
1183 if (!node_configuration_subsystem)
1186 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1187 ccf::errors::InternalError,
1188 "NodeConfigurationSubsystem is not available");
1194 node_configuration_subsystem->get()
1196 node_configuration_subsystem->get().node_config.node_data,
1200 make_read_only_endpoint(
1201 "/network/nodes/self",
1209 auto get_primary_node = [
this](
auto& args, nlohmann::json&&) {
1213 if (!primary_id.has_value())
1216 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1217 ccf::errors::InternalError,
1221 auto nodes = args.tx.ro(this->network.
nodes);
1222 auto info =
nodes->get(primary_id.value());
1226 HTTP_STATUS_NOT_FOUND,
1227 ccf::errors::ResourceNotFound,
1231 auto& ni = info.value();
1238 nodes->get_version_of_previous_write(primary_id.value())
1244 HTTP_STATUS_NOT_FOUND,
1245 ccf::errors::ResourceNotFound,
1246 "No configured consensus");
1249 make_read_only_endpoint(
1250 "/network/nodes/primary",
1257 auto head_primary = [
this](
auto& args) {
1260 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1264 args.rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT);
1268 if (!primary_id.has_value())
1270 args.rpc_ctx->set_error(
1271 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1272 ccf::errors::InternalError,
1277 auto nodes = args.tx.ro(this->network.
nodes);
1278 auto info =
nodes->get(primary_id.value());
1281 auto& interface_id =
1282 args.rpc_ctx->get_session_context()->interface_id;
1283 if (!interface_id.has_value())
1285 args.rpc_ctx->set_error(
1286 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1287 ccf::errors::InternalError,
1288 "Cannot redirect non-RPC request.");
1291 const auto& address =
1292 info->rpc_interfaces[interface_id.value()].published_address;
1293 args.rpc_ctx->set_response_header(
1294 http::headers::LOCATION,
1295 fmt::format(
"https://{}/node/primary", address));
1300 make_read_only_endpoint(
1301 "/primary", HTTP_HEAD, head_primary, no_auth_required)
1305 auto get_primary = [
this](
auto& args) {
1308 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1313 args.rpc_ctx->set_error(
1314 HTTP_STATUS_NOT_FOUND,
1315 ccf::errors::ResourceNotFound,
1316 "Node is not primary");
1320 make_read_only_endpoint(
1321 "/primary", HTTP_GET, get_primary, no_auth_required)
1325 auto get_backup = [
this](
auto& args) {
1328 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1333 args.rpc_ctx->set_error(
1334 HTTP_STATUS_NOT_FOUND,
1335 ccf::errors::ResourceNotFound,
1336 "Node is not backup");
1340 make_read_only_endpoint(
"/backup", HTTP_GET, get_backup, no_auth_required)
1344 auto consensus_config = [
this](
auto& args, nlohmann::json&&) {
1348 auto cfg =
consensus->get_latest_configuration();
1350 for (
auto& [nid, ninfo] : cfg)
1355 fmt::format(
"{}:{}", ninfo.hostname, ninfo.port)});
1362 HTTP_STATUS_NOT_FOUND,
1363 ccf::errors::ResourceNotFound,
1364 "No configured consensus");
1368 make_command_endpoint(
1377 auto consensus_state = [
this](
auto& args, nlohmann::json&&) {
1385 HTTP_STATUS_NOT_FOUND,
1386 ccf::errors::ResourceNotFound,
1387 "No configured consensus");
1391 make_command_endpoint(
1400 auto memory_usage = [](
auto& args) {
1404#ifdef INSIDE_ENCLAVE
1406 if (ccf::pal::get_mallinfo(info))
1409 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1410 args.rpc_ctx->set_response_header(
1411 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1412 args.rpc_ctx->set_response_body(nlohmann::json(mu).dump());
1417 args.rpc_ctx->set_response_status(HTTP_STATUS_INTERNAL_SERVER_ERROR);
1418 args.rpc_ctx->set_response_body(
"Failed to read memory usage");
1421 make_command_endpoint(
"/memory", HTTP_GET, memory_usage, no_auth_required)
1426 auto node_metrics = [
this](
auto& args) {
1430 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1431 args.rpc_ctx->set_response_header(
1432 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1433 args.rpc_ctx->set_response_body(nlohmann::json(nm).dump());
1436 make_command_endpoint(
1437 "/metrics", HTTP_GET, node_metrics, no_auth_required)
1442 auto js_metrics = [
this](
auto& args, nlohmann::json&&) {
1445 uint64_t bytecode_size = 0;
1446 bytecode_map->foreach(
1447 [&bytecode_size](
const auto&,
const auto& bytecode) {
1448 bytecode_size += bytecode.size();
1451 auto js_engine_map = args.tx.ro(this->network.
js_engine);
1455 version_val->get() == std::string(ccf::quickjs_version);
1466 make_read_only_endpoint(
1474 auto version = [
this](
auto&, nlohmann::json&&) {
1478#ifdef UNSAFE_VERSION
1487 make_command_endpoint(
1493 auto create = [
this](
auto& ctx, nlohmann::json&& params) {
1504 HTTP_STATUS_FORBIDDEN,
1505 ccf::errors::InternalError,
1506 "Node is not in initial state.");
1514 HTTP_STATUS_FORBIDDEN,
1515 ccf::errors::InternalError,
1516 "Service is already created.");
1520 ctx.tx, in.service_cert, in.create_txid, in.service_data, recovering);
1526 if (in.genesis_info.has_value())
1531 for (
const auto& info : in.genesis_info->members)
1537 ctx.tx, in.genesis_info->service_configuration);
1539 ctx.tx, in.genesis_info->constitution);
1543 if (in.recovery_constitution.has_value())
1546 ctx.tx, in.recovery_constitution.value());
1552 throw std::logic_error(
"Could not cast tx to CommittableTx");
1558 auto endorsed_certificates =
1560 endorsed_certificates->put(in.node_id, in.node_endorsed_certificate);
1563 in.node_info_network,
1565 in.public_encryption_key,
1568 in.measurement.hex_str(),
1569 in.certificate_signing_request,
1575 !in.snp_uvm_endorsements.has_value())
1580 ctx.tx, in.measurement, in.quote_info.format);
1587 ctx.tx, host_data, in.snp_security_policy);
1589 ctx.tx, in.snp_uvm_endorsements);
1592 std::optional<ccf::ClaimsDigest::Digest> digest =
1594 if (digest.has_value())
1596 auto digest_value = digest.value();
1597 ctx.rpc_ctx->set_claims_digest(std::move(digest_value));
1604 "/create", HTTP_POST,
json_adapter(create), no_auth_required)
1605 .set_openapi_hidden(
true)
1609 auto refresh_jwt_keys = [
this](
auto& ctx, nlohmann::json&& body) {
1613 if (!primary_id.has_value())
1617 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1618 ccf::errors::InternalError,
1619 "Primary is unknown");
1622 const auto& sig_auth_ident =
1623 ctx.template get_caller<ccf::NodeCertAuthnIdentity>();
1624 if (primary_id.value() != sig_auth_ident.node_id)
1627 "JWT key auto-refresh: request does not originate from primary");
1629 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1630 ccf::errors::InternalError,
1631 "Request does not originate from primary.");
1642 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1643 ccf::errors::InternalError,
1644 "Unable to parse body.");
1647 auto issuers = ctx.tx.ro(this->network.
jwt_issuers);
1648 auto issuer_metadata_ = issuers->get(parsed.
issuer);
1649 if (!issuer_metadata_.has_value())
1652 "JWT key auto-refresh: {} is not a valid issuer", parsed.
issuer);
1654 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1655 ccf::errors::InternalError,
1656 fmt::format(
"{} is not a valid issuer.", parsed.
issuer));
1658 auto& issuer_metadata = issuer_metadata_.value();
1660 if (!issuer_metadata.auto_refresh)
1663 "JWT key auto-refresh: {} does not have auto_refresh enabled",
1666 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1667 ccf::errors::InternalError,
1669 "{} does not have auto_refresh enabled.", parsed.
issuer));
1672 if (!set_jwt_public_signing_keys(
1680 "JWT key auto-refresh: error while storing signing keys for issuer "
1684 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1685 ccf::errors::InternalError,
1687 "Error while storing signing keys for issuer {}.",
1694 "/jwt_keys/refresh",
1697 {std::make_shared<NodeCertAuthnPolicy>()})
1698 .set_openapi_hidden(
true)
1701 auto get_jwt_metrics = [
this](
auto& args,
const nlohmann::json& params) {
1704 make_read_only_endpoint(
1705 "/jwt_keys/refresh/metrics",
1712 auto service_config_handler =
1713 [
this](
auto& args,
const nlohmann::json& params) {
1717 "/service/configuration",
1725 auto list_indexing_strategies = [
this](
1727 const nlohmann::json& params) {
1728 return make_success(this->context.get_indexing_strategies().describe());
1732 "/index/strategies",
1737 .set_auto_schema<void, nlohmann::json>()
1740 auto get_ready_app =
1742 auto node_configuration_subsystem =
1744 if (!node_configuration_subsystem)
1746 ctx.rpc_ctx->set_error(
1747 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1748 ccf::errors::InternalError,
1749 "NodeConfigurationSubsystem is not available");
1753 !node_configuration_subsystem->has_received_stop_notice() &&
1757 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1761 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1765 make_read_only_endpoint(
1766 "/ready/app", HTTP_GET, get_ready_app, no_auth_required)
1767 .set_auto_schema<void,
void>()
1771 auto get_ready_gov =
1773 auto node_configuration_subsystem =
1775 if (!node_configuration_subsystem)
1777 ctx.rpc_ctx->set_error(
1778 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1779 ccf::errors::InternalError,
1780 "NodeConfigurationSubsystem is not available");
1784 !node_configuration_subsystem->has_received_stop_notice() &&
1788 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1792 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1796 make_read_only_endpoint(
1797 "/ready/gov", HTTP_GET, get_ready_gov, no_auth_required)
1798 .set_auto_schema<void,
void>()