433 auto accept = [
this](
auto& args,
const nlohmann::json& params) {
442 HTTP_STATUS_INTERNAL_SERVER_ERROR,
443 ccf::errors::InternalError,
444 "Target node should be part of network to accept new nodes.");
450 auto this_startup_seqno =
453 in.startup_seqno.has_value() &&
454 this_startup_seqno > in.startup_seqno.value())
457 HTTP_STATUS_BAD_REQUEST,
458 ccf::errors::StartupSeqnoIsOld,
460 "Node requested to join from seqno {} which is older than this "
461 "node startup seqno {}. A snapshot at least as recent as {} must "
463 in.startup_seqno.value(),
465 this_startup_seqno));
469 auto service = args.tx.rw(this->network.
service);
471 auto active_service = service->get();
472 if (!active_service.has_value())
475 HTTP_STATUS_INTERNAL_SERVER_ERROR,
476 ccf::errors::InternalError,
477 "No service is available to accept new node.");
488 auto existing_node_info = check_node_exists(
490 args.rpc_ctx->get_session_context()->caller_cert,
491 joining_node_status);
492 if (existing_node_info.has_value())
499 this->network.ledger_secrets->get(
500 args.tx, existing_node_info->ledger_secret_seqno),
501 *this->network.identity,
502 active_service->status,
503 existing_node_info->endorsed_certificate,
512 if (primary_id.has_value())
514 const auto address = node::get_redirect_address_for_node(
515 args, args.tx, primary_id.value());
516 if (!address.has_value())
521 args.rpc_ctx->set_response_header(
522 http::headers::LOCATION,
523 fmt::format(
"https://{}/node/join", address.value()));
526 HTTP_STATUS_PERMANENT_REDIRECT,
527 ccf::errors::NodeCannotHandleRequest,
528 "Node is not primary; cannot handle write");
532 HTTP_STATUS_INTERNAL_SERVER_ERROR,
533 ccf::errors::InternalError,
539 args.rpc_ctx->get_session_context()->caller_cert,
542 active_service->status);
550 auto existing_node_info = check_node_exists(
551 args.tx, args.rpc_ctx->get_session_context()->caller_cert);
552 if (existing_node_info.has_value())
558 auto node_info =
nodes->get(existing_node_info->node_id);
559 auto node_status = node_info->status;
561 rep.
node_id = existing_node_info->node_id;
562 if (is_taking_part_in_acking(node_status))
567 this->network.ledger_secrets->get(
568 args.tx, existing_node_info->ledger_secret_seqno),
569 *this->network.identity,
570 active_service->status,
571 existing_node_info->endorsed_certificate,
584 HTTP_STATUS_BAD_REQUEST,
585 ccf::errors::InvalidNodeState,
587 "Joining node is not in expected state ({}).", node_status));
593 if (primary_id.has_value())
595 const auto address = node::get_redirect_address_for_node(
596 args, args.tx, primary_id.value());
597 if (!address.has_value())
602 args.rpc_ctx->set_response_header(
603 http::headers::LOCATION,
604 fmt::format(
"https://{}/node/join", address.value()));
607 HTTP_STATUS_PERMANENT_REDIRECT,
608 ccf::errors::NodeCannotHandleRequest,
609 "Node is not primary; cannot handle write");
613 HTTP_STATUS_INTERNAL_SERVER_ERROR,
614 ccf::errors::InternalError,
621 args.rpc_ctx->get_session_context()->caller_cert,
624 active_service->status);
626 make_endpoint(
"/join", HTTP_POST,
json_adapter(accept), no_auth_required)
628 .set_openapi_hidden(
true)
631 auto set_retired_committed = [
this](
auto& ctx, nlohmann::json&&) {
633 nodes->foreach([&
nodes](
const auto& node_id,
auto node_info) {
634 auto gc_node =
nodes->get_globally_committed(node_id);
636 gc_node.has_value() &&
638 !node_info.retired_committed)
642 node_info.retired_committed =
true;
643 nodes->put(node_id, node_info);
645 LOG_DEBUG_FMT(
"Setting retired_committed on node {}", node_id);
653 "network/nodes/set_retired_committed",
656 {std::make_shared<NodeCertAuthnPolicy>()})
657 .set_openapi_hidden(
true)
660 auto get_state = [
this](
auto& args, nlohmann::json&&) {
662 auto [s, rts, lrs] = this->node_operation.
state();
663 result.
node_id = this->context.get_node_id();
670 auto signatures = args.tx.template ro<Signatures>(Tables::SIGNATURES);
671 auto sig = signatures->get();
672 if (!sig.has_value())
681 auto node_configuration_subsystem =
683 if (!node_configuration_subsystem)
686 HTTP_STATUS_INTERNAL_SERVER_ERROR,
687 ccf::errors::InternalError,
688 "NodeConfigurationSubsystem is not available");
691 node_configuration_subsystem->has_received_stop_notice();
695 make_read_only_endpoint(
701 auto get_quote = [
this](
auto& args, nlohmann::json&&) {
704 get_quote_for_this_node_v1(args.tx, node_quote_info);
708 q.
node_id = context.get_node_id();
715 auto node_info =
nodes->get(context.get_node_id());
716 if (node_info.has_value() && node_info->code_digest.has_value())
724 if (measurement.has_value())
731 HTTP_STATUS_INTERNAL_SERVER_ERROR,
732 ccf::errors::InvalidQuote,
733 "Failed to extract code id from node quote.");
743 HTTP_STATUS_NOT_FOUND,
744 ccf::errors::ResourceNotFound,
745 "Could not find node quote.");
749 HTTP_STATUS_INTERNAL_SERVER_ERROR,
750 ccf::errors::InternalError,
753 make_read_only_endpoint(
758 .set_auto_schema<void,
Quote>()
761 make_read_only_endpoint(
762 "/attestations/self",
770 auto get_quotes = [
this](
auto& args, nlohmann::json&&) {
775 const auto& node_id,
const auto& node_info) {
780 q.
raw = node_info.quote_info.quote;
782 q.
format = node_info.quote_info.format;
785 if (node_info.code_digest.has_value())
793 if (measurement.has_value())
798 quotes.emplace_back(q);
805 make_read_only_endpoint(
813 auto get_attestations =
814 [get_quotes](
auto& args, nlohmann::json&& params) {
815 auto res = get_quotes(args, std::move(params));
816 const auto* body = std::get_if<nlohmann::json>(&res);
819 auto result = nlohmann::json::object();
820 result[
"attestations"] = (*body)[
"quotes"];
826 make_read_only_endpoint(
834 auto network_status = [
this](
auto& args, nlohmann::json&&) {
836 auto service = args.tx.ro(network.
service);
837 auto service_state = service->get();
838 if (service_state.has_value())
840 const auto& service_value = service_state.value();
846 service_value.current_service_create_txid;
851 if (primary_id.has_value())
859 HTTP_STATUS_NOT_FOUND,
860 ccf::errors::ResourceNotFound,
861 "Service state not available.");
863 make_read_only_endpoint(
871 auto service_previous_identity = [](
auto& args, nlohmann::json&&) {
872 auto psi_handle = args.tx.template ro<ccf::PreviousServiceIdentity>(
873 ccf::Tables::PREVIOUS_SERVICE_IDENTITY);
874 const auto psi = psi_handle->get();
883 HTTP_STATUS_NOT_FOUND,
884 ccf::errors::ResourceNotFound,
885 "This service is not a recovery of a previous service.");
887 make_read_only_endpoint(
888 "/service/previous_identity",
895 auto get_nodes = [
this](
auto& args, nlohmann::json&&) {
896 const auto parsed_query =
897 http::parse_query(args.rpc_ctx->get_request_query());
900 const auto host = http::get_query_value_opt<std::string>(
902 const auto port = http::get_query_value_opt<std::string>(
904 const auto status_str = http::get_query_value_opt<std::string>(
907 std::optional<NodeStatus> status;
908 if (status_str.has_value())
914 status = nlohmann::json(status_str.value()).get<
NodeStatus>();
919 HTTP_STATUS_BAD_REQUEST,
920 ccf::errors::InvalidQueryParameterValue,
922 "Query parameter '{}' is not a valid node status",
923 status_str.value()));
932 if (status.has_value() && status.value() != ni.
status)
938 bool is_matched =
false;
941 const auto& [pub_host, pub_port] =
942 split_net_address(interface.second.published_address);
945 (!
host.has_value() ||
host.value() == pub_host) &&
946 (!port.has_value() || port.value() == pub_port))
958 bool is_primary =
false;
961 is_primary =
consensus->primary() == nid;
970 nodes->get_version_of_previous_write(nid).value_or(0)});
976 make_read_only_endpoint(
982 .add_query_parameter<std::string>(
984 .add_query_parameter<std::string>(
986 .add_query_parameter<std::string>(
990 auto get_removable_nodes = [
this](
auto& args, nlohmann::json&&) {
1002 auto node =
nodes->get_globally_committed(node_id);
1005 node->retired_committed)
1007 out.
nodes.push_back(
1011 node->rpc_interfaces,
1013 nodes->get_version_of_previous_write(node_id).value_or(0)});
1021 make_read_only_endpoint(
1022 "/network/removable_nodes",
1029 auto delete_retired_committed_node =
1030 [
this](
auto& args, nlohmann::json&&) {
1033 std::string node_id;
1035 if (!get_path_param(
1036 args.rpc_ctx->get_request_path_params(),
1042 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName,
error);
1045 auto nodes = args.tx.rw(this->network.
nodes);
1046 if (!
nodes->has(node_id))
1049 HTTP_STATUS_NOT_FOUND,
1050 ccf::errors::ResourceNotFound,
1054 auto node_endorsed_certificates =
1068 auto node =
nodes->get_globally_committed(node_id);
1071 node->retired_committed)
1073 nodes->remove(node_id);
1074 node_endorsed_certificates->remove(node_id);
1079 HTTP_STATUS_BAD_REQUEST,
1080 ccf::errors::NodeNotRetiredCommitted,
1081 "Node is not completely retired");
1088 "/network/nodes/{node_id}",
1092 .set_auto_schema<void,
bool>()
1095 auto get_self_signed_certificate =
1096 [
this](
auto& , nlohmann::json&&) {
1100 make_command_endpoint(
1101 "/self_signed_certificate",
1109 auto get_node_info = [
this](
auto& args, nlohmann::json&&) {
1110 std::string node_id;
1112 if (!get_path_param(
1113 args.rpc_ctx->get_request_path_params(),
1119 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName,
error);
1122 auto nodes = args.tx.ro(this->network.
nodes);
1123 auto info =
nodes->get(node_id);
1128 HTTP_STATUS_NOT_FOUND,
1129 ccf::errors::ResourceNotFound,
1133 bool is_primary =
false;
1137 if (primary.has_value() && primary.value() == node_id)
1142 auto& ni = info.value();
1149 nodes->get_version_of_previous_write(node_id).value_or(0)});
1151 make_read_only_endpoint(
1152 "/network/nodes/{node_id}",
1159 auto get_self_node = [
this](
auto& args, nlohmann::json&&) {
1160 auto node_id = this->context.get_node_id();
1161 auto nodes = args.tx.ro(this->network.
nodes);
1162 auto info =
nodes->get(node_id);
1164 bool is_primary =
false;
1168 if (primary.has_value() && primary.value() == node_id)
1174 if (info.has_value())
1178 auto& ni = info.value();
1185 nodes->get_version_of_previous_write(node_id).value_or(0)});
1189 auto node_configuration_subsystem =
1191 if (!node_configuration_subsystem)
1194 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1195 ccf::errors::InternalError,
1196 "NodeConfigurationSubsystem is not available");
1198 const auto& node_startup_config =
1199 node_configuration_subsystem->get().node_config;
1205 node_startup_config.node_data,
1208 make_read_only_endpoint(
1209 "/network/nodes/self",
1217 auto get_primary_node = [
this](
auto& args, nlohmann::json&&) {
1221 if (!primary_id.has_value())
1224 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1225 ccf::errors::InternalError,
1229 auto nodes = args.tx.ro(this->network.
nodes);
1230 auto info =
nodes->get(primary_id.value());
1234 HTTP_STATUS_NOT_FOUND,
1235 ccf::errors::ResourceNotFound,
1239 auto& ni = info.value();
1246 nodes->get_version_of_previous_write(primary_id.value())
1251 HTTP_STATUS_NOT_FOUND,
1252 ccf::errors::ResourceNotFound,
1253 "No configured consensus");
1255 make_read_only_endpoint(
1256 "/network/nodes/primary",
1263 auto head_primary = [
this](
auto& args) {
1266 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1272 args.rpc_ctx->set_error(
1273 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1274 ccf::errors::InternalError,
1275 "Consensus not initialised");
1280 if (!primary_id.has_value())
1282 args.rpc_ctx->set_error(
1283 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1284 ccf::errors::InternalError,
1289 const auto address = node::get_redirect_address_for_node(
1290 args, args.tx, primary_id.value());
1291 if (!address.has_value())
1296 args.rpc_ctx->set_response_header(
1297 http::headers::LOCATION,
1298 fmt::format(
"https://{}/node/primary", address.value()));
1299 args.rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT);
1302 make_read_only_endpoint(
1303 "/primary", HTTP_HEAD, head_primary, no_auth_required)
1307 auto get_primary = [
this](
auto& args) {
1310 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1314 args.rpc_ctx->set_error(
1315 HTTP_STATUS_NOT_FOUND,
1316 ccf::errors::ResourceNotFound,
1317 "Node is not primary");
1319 make_read_only_endpoint(
1320 "/primary", HTTP_GET, get_primary, no_auth_required)
1324 auto get_backup = [
this](
auto& args) {
1327 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1331 args.rpc_ctx->set_error(
1332 HTTP_STATUS_NOT_FOUND,
1333 ccf::errors::ResourceNotFound,
1334 "Node is not backup");
1336 make_read_only_endpoint(
"/backup", HTTP_GET, get_backup, no_auth_required)
1340 auto consensus_config = [
this](
auto& , nlohmann::json&&) {
1344 auto cfg =
consensus->get_latest_configuration();
1346 for (
auto& [nid, ninfo] : cfg)
1351 fmt::format(
"{}:{}", ninfo.hostname, ninfo.port)});
1357 HTTP_STATUS_NOT_FOUND,
1358 ccf::errors::ResourceNotFound,
1359 "No configured consensus");
1362 make_command_endpoint(
1371 auto consensus_state = [
this](
auto& , nlohmann::json&&) {
1378 HTTP_STATUS_NOT_FOUND,
1379 ccf::errors::ResourceNotFound,
1380 "No configured consensus");
1383 make_command_endpoint(
1392 auto memory_usage = [](
auto& args) {
1394 if (ccf::pal::get_mallinfo(info))
1397 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1398 args.rpc_ctx->set_response_header(
1399 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1400 args.rpc_ctx->set_response_body(nlohmann::json(mu).dump());
1404 args.rpc_ctx->set_response_status(HTTP_STATUS_INTERNAL_SERVER_ERROR);
1405 args.rpc_ctx->set_response_body(
"Failed to read memory usage");
1408 make_command_endpoint(
"/memory", HTTP_GET, memory_usage, no_auth_required)
1413 auto node_metrics = [
this](
auto& args) {
1417 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1418 args.rpc_ctx->set_response_header(
1419 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1420 args.rpc_ctx->set_response_body(nlohmann::json(nm).dump());
1423 make_command_endpoint(
1424 "/metrics", HTTP_GET, node_metrics, no_auth_required)
1429 auto js_metrics = [
this](
auto& args, nlohmann::json&&) {
1432 uint64_t bytecode_size = 0;
1433 bytecode_map->foreach(
1434 [&bytecode_size](
const auto&,
const auto& bytecode) {
1435 bytecode_size += bytecode.size();
1438 auto js_engine_map = args.tx.ro(this->network.
js_engine);
1442 version_val->get() == std::string(ccf::quickjs_version);
1453 make_read_only_endpoint(
1461 auto version = [](
auto&, nlohmann::json&&) {
1470 make_command_endpoint(
1476 auto create = [
this](
auto& ctx, nlohmann::json&& params) {
1487 HTTP_STATUS_FORBIDDEN,
1488 ccf::errors::InternalError,
1489 "Node is not in initial state.");
1497 HTTP_STATUS_FORBIDDEN,
1498 ccf::errors::InternalError,
1499 "Service is already created.");
1503 ctx.tx, in.service_cert, in.create_txid, in.service_data, recovering);
1509 if (in.genesis_info.has_value())
1514 for (
const auto& info : in.genesis_info->members)
1520 ctx.tx, in.genesis_info->service_configuration);
1522 ctx.tx, in.genesis_info->constitution);
1530 throw std::logic_error(
"Could not cast tx to CommittableTx");
1536 auto endorsed_certificates =
1538 endorsed_certificates->put(in.node_id, in.node_endorsed_certificate);
1541 in.node_info_network,
1543 in.public_encryption_key,
1546 in.measurement.hex_str(),
1547 in.certificate_signing_request,
1554 !in.snp_uvm_endorsements.has_value())
1559 ctx.tx, in.measurement, in.quote_info.format);
1562 switch (in.quote_info.format)
1567 if (host_data.has_value())
1570 ctx.tx, host_data.value());
1574 LOG_FAIL_FMT(
"Unable to extract host data from virtual quote");
1584 ctx.tx, host_data, in.snp_security_policy);
1587 ctx.tx, in.snp_uvm_endorsements);
1592 ctx.tx, attestation);
1602 std::optional<ccf::ClaimsDigest::Digest> digest =
1604 if (digest.has_value())
1606 auto digest_value = digest.value();
1607 ctx.rpc_ctx->set_claims_digest(std::move(digest_value));
1614 "/create", HTTP_POST,
json_adapter(create), no_auth_required)
1615 .set_openapi_hidden(
true)
1619 auto refresh_jwt_keys = [
this](
auto& ctx, nlohmann::json&& body) {
1623 if (!primary_id.has_value())
1627 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1628 ccf::errors::InternalError,
1629 "Primary is unknown");
1632 const auto& sig_auth_ident =
1633 ctx.template get_caller<ccf::NodeCertAuthnIdentity>();
1634 if (primary_id.value() != sig_auth_ident.node_id)
1637 "JWT key auto-refresh: request does not originate from primary");
1639 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1640 ccf::errors::InternalError,
1641 "Request does not originate from primary.");
1652 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1653 ccf::errors::InternalError,
1654 "Unable to parse body.");
1657 auto issuers = ctx.tx.ro(this->network.
jwt_issuers);
1658 auto issuer_metadata_ = issuers->get(parsed.
issuer);
1659 if (!issuer_metadata_.has_value())
1662 "JWT key auto-refresh: {} is not a valid issuer", parsed.
issuer);
1664 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1665 ccf::errors::InternalError,
1666 fmt::format(
"{} is not a valid issuer.", parsed.
issuer));
1668 auto& issuer_metadata = issuer_metadata_.value();
1670 if (!issuer_metadata.auto_refresh)
1673 "JWT key auto-refresh: {} does not have auto_refresh enabled",
1676 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1677 ccf::errors::InternalError,
1679 "{} does not have auto_refresh enabled.", parsed.
issuer));
1682 if (!set_jwt_public_signing_keys(
1690 "JWT key auto-refresh: error while storing signing keys for issuer "
1694 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1695 ccf::errors::InternalError,
1697 "Error while storing signing keys for issuer {}.",
1704 "/jwt_keys/refresh",
1707 {std::make_shared<NodeCertAuthnPolicy>()})
1708 .set_openapi_hidden(
true)
1711 auto get_jwt_metrics =
1712 [
this](
auto& ,
const nlohmann::json& ) {
1715 make_read_only_endpoint(
1716 "/jwt_keys/refresh/metrics",
1723 auto service_config_handler =
1724 [
this](
auto& args,
const nlohmann::json& ) {
1728 "/service/configuration",
1736 auto list_indexing_strategies = [
this](
1738 const nlohmann::json& ) {
1739 return make_success(this->context.get_indexing_strategies().describe());
1743 "/index/strategies",
1748 .set_auto_schema<void, nlohmann::json>()
1751 auto get_ready_app =
1753 auto node_configuration_subsystem =
1755 if (!node_configuration_subsystem)
1757 ctx.rpc_ctx->set_error(
1758 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1759 ccf::errors::InternalError,
1760 "NodeConfigurationSubsystem is not available");
1764 !node_configuration_subsystem->has_received_stop_notice() &&
1768 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1772 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1776 make_read_only_endpoint(
1777 "/ready/app", HTTP_GET, get_ready_app, no_auth_required)
1778 .set_auto_schema<void,
void>()
1782 auto get_ready_gov =
1784 auto node_configuration_subsystem =
1786 if (!node_configuration_subsystem)
1788 ctx.rpc_ctx->set_error(
1789 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1790 ccf::errors::InternalError,
1791 "NodeConfigurationSubsystem is not available");
1795 !node_configuration_subsystem->has_received_stop_notice() &&
1799 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1803 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1807 make_read_only_endpoint(
1808 "/ready/gov", HTTP_GET, get_ready_gov, no_auth_required)
1809 .set_auto_schema<void,
void>()
1813 ccf::node::init_file_serving_handlers(*
this, context);