454 auto accept = [
this](
auto& args,
const nlohmann::json& params) {
463 const std::string payload =
464 "Target node should be part of network to accept new nodes.";
467 HTTP_STATUS_INTERNAL_SERVER_ERROR,
468 ccf::errors::InternalError,
473 auto service = args.tx.rw(this->network.
service);
474 auto active_service = service->get();
475 if (!active_service.has_value())
477 const std::string payload =
478 "No service is available to accept new node.";
481 HTTP_STATUS_INTERNAL_SERVER_ERROR,
482 ccf::errors::InternalError,
489 auto existing_node_info = check_node_exists(
490 args.tx, args.rpc_ctx->get_session_context()->caller_cert);
491 if (existing_node_info.has_value())
497 auto node_info =
nodes->get(existing_node_info->node_id);
498 auto node_status = node_info->status;
500 rep.
node_id = existing_node_info->node_id;
506 this->network.ledger_secrets->get(
507 args.tx, existing_node_info->ledger_secret_seqno),
508 *this->network.identity,
509 active_service->status,
510 existing_node_info->endorsed_certificate,
514 "Join request accepted: {} already marked as TRUSTED",
515 existing_node_info->node_id);
523 "Join request accepted: {} already marked as PENDING",
524 existing_node_info->node_id);
528 const std::string payload = fmt::format(
529 "Joining node is not in expected state ({}).", node_status);
532 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidNodeState, payload);
539 if (primary_id.has_value())
541 const auto address = node::get_redirect_address_for_node(
542 args, args.tx, primary_id.value());
543 if (!address.has_value())
546 "Join request rejected: no redirect address for "
552 args.rpc_ctx->set_response_header(
553 http::headers::LOCATION,
554 fmt::format(
"https://{}/node/join", address.value()));
556 const std::string payload =
557 "Node is not primary; cannot handle write";
559 "Join request redirected to primary {} at {}: {}",
564 HTTP_STATUS_PERMANENT_REDIRECT,
565 ccf::errors::NodeCannotHandleRequest,
569 const std::string payload =
"Primary unknown";
572 HTTP_STATUS_INTERNAL_SERVER_ERROR,
573 ccf::errors::InternalError,
588 auto this_startup_seqno =
595 bool using_preferred_bound =
596 (in.join_fetch_count.has_value() && in.join_fetch_count.value() == 0);
597 if (using_preferred_bound)
599 auto node_configuration_subsystem =
601 if (node_configuration_subsystem !=
nullptr)
603 const auto& snapshots_config =
605 const auto latest_committed_snapshot =
607 snapshots_config.directory);
608 if (latest_committed_snapshot.has_value())
610 const auto latest_snapshot_seqno =
611 snapshots::get_snapshot_idx_from_file_name(
612 latest_committed_snapshot->filename().string());
613 required_seqno = std::max(
620 in.startup_seqno.has_value() &&
621 in.startup_seqno.value() < required_seqno)
626 const std::string payload = fmt::format(
627 "Node requested to join from seqno {} which is older than this "
628 "node {} {}. A snapshot at least as recent as {} must "
630 in.startup_seqno.value(),
631 using_preferred_bound ?
"latest_on_disk_seqno" :
"startup_seqno",
636 HTTP_STATUS_BAD_REQUEST, ccf::errors::StartupSeqnoIsOld, payload);
650 args.rpc_ctx->get_session_context()->caller_cert,
653 active_service->status);
655 make_endpoint(
"/join", HTTP_POST,
json_adapter(accept), no_auth_required)
657 .set_openapi_hidden(
true)
660 auto set_retired_committed = [
this](
auto& ctx, nlohmann::json&&) {
662 nodes->foreach([&
nodes](
const auto& node_id,
auto node_info) {
663 auto gc_node =
nodes->get_globally_committed(node_id);
665 gc_node.has_value() &&
667 !node_info.retired_committed)
671 node_info.retired_committed =
true;
672 nodes->put(node_id, node_info);
674 LOG_DEBUG_FMT(
"Setting retired_committed on node {}", node_id);
682 "network/nodes/set_retired_committed",
685 {std::make_shared<NodeCertAuthnPolicy>()})
686 .set_openapi_hidden(
true)
689 auto get_state = [
this](
auto& args, nlohmann::json&&) {
691 auto [s, rts, lrs] = this->node_operation.
state();
692 result.
node_id = this->context.get_node_id();
700 auto signatures = args.tx.template ro<Signatures>(Tables::SIGNATURES);
701 auto sig = signatures->get();
706 raw_seqno = sig.value().seqno;
710 auto cose_signatures =
711 args.tx.template ro<CoseSignatures>(Tables::COSE_SIGNATURES);
712 auto cose_sig = cose_signatures->get();
713 if (cose_sig.has_value() && !cose_sig->empty())
715 auto receipt = ccf::cose::decode_ccf_receipt(cose_sig.value(),
false);
717 if (!txid.has_value())
719 throw std::logic_error(fmt::format(
720 "Failed to parse txid from COSE signature: {}",
721 receipt.phdr.ccf.txid));
723 cose_seqno = txid->seqno;
728 auto node_configuration_subsystem =
730 if (!node_configuration_subsystem)
733 HTTP_STATUS_INTERNAL_SERVER_ERROR,
734 ccf::errors::InternalError,
735 "NodeConfigurationSubsystem is not available");
738 node_configuration_subsystem->has_received_stop_notice();
742 make_read_only_endpoint(
748 auto get_quote = [
this](
auto& args, nlohmann::json&&) {
751 get_quote_for_this_node_v1(args.tx, node_quote_info);
755 q.
node_id = context.get_node_id();
762 auto node_info =
nodes->get(context.get_node_id());
763 if (node_info.has_value() && node_info->code_digest.has_value())
771 if (measurement.has_value())
778 HTTP_STATUS_INTERNAL_SERVER_ERROR,
779 ccf::errors::InvalidQuote,
780 "Failed to extract code id from node quote.");
790 HTTP_STATUS_NOT_FOUND,
791 ccf::errors::ResourceNotFound,
792 "Could not find node quote.");
796 HTTP_STATUS_INTERNAL_SERVER_ERROR,
797 ccf::errors::InternalError,
800 make_read_only_endpoint(
805 .set_auto_schema<void,
Quote>()
808 make_read_only_endpoint(
809 "/attestations/self",
817 auto get_quotes = [
this](
auto& args, nlohmann::json&&) {
822 const auto& node_id,
const auto& node_info) {
827 q.
raw = node_info.quote_info.quote;
829 q.
format = node_info.quote_info.format;
832 if (node_info.code_digest.has_value())
840 if (measurement.has_value())
845 quotes.emplace_back(q);
852 make_read_only_endpoint(
860 auto get_attestations =
861 [get_quotes](
auto& args, nlohmann::json&& params) {
862 auto res = get_quotes(args, std::move(params));
863 const auto* body = std::get_if<nlohmann::json>(&res);
866 auto result = nlohmann::json::object();
867 result[
"attestations"] = (*body)[
"quotes"];
873 make_read_only_endpoint(
881 auto network_status = [
this](
auto& args, nlohmann::json&&) {
883 auto service = args.tx.ro(network.
service);
884 auto service_state = service->get();
885 if (service_state.has_value())
887 const auto& service_value = service_state.value();
893 service_value.current_service_create_txid;
898 if (primary_id.has_value())
906 HTTP_STATUS_NOT_FOUND,
907 ccf::errors::ResourceNotFound,
908 "Service state not available.");
910 make_read_only_endpoint(
918 auto service_previous_identity = [](
auto& args, nlohmann::json&&) {
919 auto psi_handle = args.tx.template ro<ccf::PreviousServiceIdentity>(
920 ccf::Tables::PREVIOUS_SERVICE_IDENTITY);
921 const auto psi = psi_handle->get();
930 HTTP_STATUS_NOT_FOUND,
931 ccf::errors::ResourceNotFound,
932 "This service is not a recovery of a previous service.");
934 make_read_only_endpoint(
935 "/service/previous_identity",
942 auto get_nodes = [
this](
auto& args, nlohmann::json&&) {
943 const auto parsed_query =
944 http::parse_query(args.rpc_ctx->get_request_query());
947 const auto host = http::get_query_value_opt<std::string>(
949 const auto port = http::get_query_value_opt<std::string>(
951 const auto status_str = http::get_query_value_opt<std::string>(
954 std::optional<NodeStatus> status;
955 if (status_str.has_value())
961 status = nlohmann::json(status_str.value()).get<
NodeStatus>();
966 HTTP_STATUS_BAD_REQUEST,
967 ccf::errors::InvalidQueryParameterValue,
969 "Query parameter '{}' is not a valid node status",
970 status_str.value()));
979 if (status.has_value() && status.value() != ni.
status)
985 bool is_matched =
false;
988 const auto& [pub_host, pub_port] =
989 split_net_address(interface.second.published_address);
992 (!
host.has_value() ||
host.value() == pub_host) &&
993 (!port.has_value() || port.value() == pub_port))
1005 bool is_primary =
false;
1008 is_primary =
consensus->primary() == nid;
1011 out.
nodes.push_back(
1017 nodes->get_version_of_previous_write(nid).value_or(0)});
1023 make_read_only_endpoint(
1029 .add_query_parameter<std::string>(
1031 .add_query_parameter<std::string>(
1033 .add_query_parameter<std::string>(
1037 auto get_removable_nodes = [
this](
auto& args, nlohmann::json&&) {
1040 auto nodes = args.tx.ro(this->network.
nodes);
1049 auto node =
nodes->get_globally_committed(node_id);
1052 node->retired_committed)
1054 out.
nodes.push_back(
1058 node->rpc_interfaces,
1060 nodes->get_version_of_previous_write(node_id).value_or(0)});
1068 make_read_only_endpoint(
1069 "/network/removable_nodes",
1076 auto delete_retired_committed_node = [
this](
1077 auto& args, nlohmann::json&&) {
1080 std::string node_id;
1082 if (!get_path_param(
1083 args.rpc_ctx->get_request_path_params(),
1089 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName,
error);
1092 auto nodes = args.tx.rw(this->network.
nodes);
1093 if (!
nodes->has(node_id))
1096 HTTP_STATUS_NOT_FOUND,
1097 ccf::errors::ResourceNotFound,
1101 auto node_endorsed_certificates =
1115 auto node =
nodes->get_globally_committed(node_id);
1118 node->retired_committed)
1120 nodes->remove(node_id);
1121 node_endorsed_certificates->remove(node_id);
1124 auto* local_sealing_node_id_map =
1125 args.tx.template rw<LocalSealingNodeIdMap>(
1126 Tables::SEALING_RECOVERY_NAMES);
1127 local_sealing_node_id_map->foreach(
1129 const auto& sealing_recovery_name,
const auto& sealing_node_id) {
1130 if (sealing_node_id == node_id)
1132 local_sealing_node_id_map->remove(sealing_recovery_name);
1137 auto* sealed_recovery_keys = args.tx.template rw<SealedRecoveryKeys>(
1138 Tables::SEALED_RECOVERY_KEYS);
1139 sealed_recovery_keys->remove(node_id);
1144 HTTP_STATUS_BAD_REQUEST,
1145 ccf::errors::NodeNotRetiredCommitted,
1146 "Node is not completely retired");
1153 "/network/nodes/{node_id}",
1157 .set_auto_schema<void,
bool>()
1160 auto get_self_signed_certificate =
1161 [
this](
auto& , nlohmann::json&&) {
1165 make_command_endpoint(
1166 "/self_signed_certificate",
1174 auto get_node_info = [
this](
auto& args, nlohmann::json&&) {
1175 std::string node_id;
1177 if (!get_path_param(
1178 args.rpc_ctx->get_request_path_params(),
1184 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName,
error);
1187 auto nodes = args.tx.ro(this->network.
nodes);
1188 auto info =
nodes->get(node_id);
1193 HTTP_STATUS_NOT_FOUND,
1194 ccf::errors::ResourceNotFound,
1198 bool is_primary =
false;
1202 if (primary.has_value() && primary.value() == node_id)
1207 auto& ni = info.value();
1214 nodes->get_version_of_previous_write(node_id).value_or(0)});
1216 make_read_only_endpoint(
1217 "/network/nodes/{node_id}",
1224 auto get_self_node = [
this](
auto& args, nlohmann::json&&) {
1225 auto node_id = this->context.get_node_id();
1226 auto nodes = args.tx.ro(this->network.
nodes);
1227 auto info =
nodes->get(node_id);
1229 bool is_primary =
false;
1233 if (primary.has_value() && primary.value() == node_id)
1239 if (info.has_value())
1243 auto& ni = info.value();
1250 nodes->get_version_of_previous_write(node_id).value_or(0)});
1254 auto node_configuration_subsystem =
1256 if (!node_configuration_subsystem)
1259 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1260 ccf::errors::InternalError,
1261 "NodeConfigurationSubsystem is not available");
1263 const auto& node_startup_config =
1264 node_configuration_subsystem->get().node_config;
1270 node_startup_config.node_data,
1273 make_read_only_endpoint(
1274 "/network/nodes/self",
1282 auto get_primary_node = [
this](
auto& args, nlohmann::json&&) {
1286 if (!primary_id.has_value())
1289 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1290 ccf::errors::InternalError,
1294 auto nodes = args.tx.ro(this->network.
nodes);
1295 auto info =
nodes->get(primary_id.value());
1299 HTTP_STATUS_NOT_FOUND,
1300 ccf::errors::ResourceNotFound,
1304 auto& ni = info.value();
1311 nodes->get_version_of_previous_write(primary_id.value())
1316 HTTP_STATUS_NOT_FOUND,
1317 ccf::errors::ResourceNotFound,
1318 "No configured consensus");
1320 make_read_only_endpoint(
1321 "/network/nodes/primary",
1328 auto head_primary = [
this](
auto& args) {
1331 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1337 args.rpc_ctx->set_error(
1338 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1339 ccf::errors::InternalError,
1340 "Consensus not initialised");
1345 if (!primary_id.has_value())
1347 args.rpc_ctx->set_error(
1348 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1349 ccf::errors::InternalError,
1354 const auto address = node::get_redirect_address_for_node(
1355 args, args.tx, primary_id.value());
1356 if (!address.has_value())
1361 args.rpc_ctx->set_response_header(
1362 http::headers::LOCATION,
1363 fmt::format(
"https://{}/node/primary", address.value()));
1364 args.rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT);
1367 make_read_only_endpoint(
1368 "/primary", HTTP_HEAD, head_primary, no_auth_required)
1372 auto get_primary = [
this](
auto& args) {
1375 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1379 args.rpc_ctx->set_error(
1380 HTTP_STATUS_NOT_FOUND,
1381 ccf::errors::ResourceNotFound,
1382 "Node is not primary");
1384 make_read_only_endpoint(
1385 "/primary", HTTP_GET, get_primary, no_auth_required)
1389 auto get_backup = [
this](
auto& args) {
1392 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1396 args.rpc_ctx->set_error(
1397 HTTP_STATUS_NOT_FOUND,
1398 ccf::errors::ResourceNotFound,
1399 "Node is not backup");
1401 make_read_only_endpoint(
"/backup", HTTP_GET, get_backup, no_auth_required)
1405 auto consensus_config = [
this](
auto& , nlohmann::json&&) {
1409 auto cfg =
consensus->get_latest_configuration();
1411 for (
auto& [nid, ninfo] : cfg)
1416 fmt::format(
"{}:{}", ninfo.hostname, ninfo.port)});
1422 HTTP_STATUS_NOT_FOUND,
1423 ccf::errors::ResourceNotFound,
1424 "No configured consensus");
1427 make_command_endpoint(
1436 auto consensus_state = [
this](
auto& , nlohmann::json&&) {
1443 HTTP_STATUS_NOT_FOUND,
1444 ccf::errors::ResourceNotFound,
1445 "No configured consensus");
1448 make_command_endpoint(
1457 auto node_metrics = [
this](
auto& args) {
1461 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1462 args.rpc_ctx->set_response_header(
1463 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1464 args.rpc_ctx->set_response_body(nlohmann::json(nm).dump());
1467 make_command_endpoint(
1468 "/metrics", HTTP_GET, node_metrics, no_auth_required)
1473 auto js_metrics = [
this](
auto& args, nlohmann::json&&) {
1476 uint64_t bytecode_size = 0;
1477 bytecode_map->foreach(
1478 [&bytecode_size](
const auto&,
const auto& bytecode) {
1479 bytecode_size += bytecode.size();
1482 auto js_engine_map = args.tx.ro(this->network.
js_engine);
1486 version_val->get() == std::string(ccf::quickjs_version);
1497 make_read_only_endpoint(
1505 auto version = [](
auto&, nlohmann::json&&) {
1514 make_command_endpoint(
1520 auto create = [
this](
auto& ctx, nlohmann::json&& params) {
1531 HTTP_STATUS_FORBIDDEN,
1532 ccf::errors::InternalError,
1533 "Node is not in initial state.");
1536 const auto& sig_auth_ident =
1537 ctx.template get_caller<ccf::AnyCertAuthnIdentity>();
1539 const auto caller_node_id =
1541 if (caller_node_id != this->context.get_node_id())
1544 HTTP_STATUS_FORBIDDEN,
1545 ccf::errors::AuthorizationFailed,
1546 "Only the node itself can call this endpoint.");
1554 HTTP_STATUS_FORBIDDEN,
1555 ccf::errors::InternalError,
1556 "Service is already created.");
1560 ctx.tx, in.service_cert, in.create_txid, in.service_data, recovering);
1566 if (in.genesis_info.has_value())
1571 for (
const auto& info : in.genesis_info->members)
1577 ctx.tx, in.genesis_info->service_configuration);
1579 ctx.tx, in.genesis_info->constitution);
1587 throw std::logic_error(
"Could not cast tx to CommittableTx");
1593 auto endorsed_certificates =
1595 endorsed_certificates->put(in.node_id, in.node_endorsed_certificate);
1598 in.node_info_network,
1600 in.public_encryption_key,
1603 in.measurement.hex_str(),
1604 in.certificate_signing_request,
1609 if (in.sealing_recovery_data.has_value())
1611 const auto& [sealing_keys, sealing_recovery_name] =
1612 in.sealing_recovery_data.value();
1613 auto* sealed_recovery_keys = ctx.tx.template rw<SealedRecoveryKeys>(
1614 Tables::SEALED_RECOVERY_KEYS);
1615 sealed_recovery_keys->put(in.node_id, sealing_keys);
1617 auto* local_sealing_node_id_map =
1618 ctx.tx.template rw<LocalSealingNodeIdMap>(
1619 Tables::SEALING_RECOVERY_NAMES);
1620 local_sealing_node_id_map->put(sealing_recovery_name, in.node_id);
1627 !in.snp_uvm_endorsements.has_value())
1632 ctx.tx, in.measurement, in.quote_info.format);
1635 switch (in.quote_info.format)
1640 if (host_data.has_value())
1643 ctx.tx, host_data.value());
1647 LOG_FAIL_FMT(
"Unable to extract host data from virtual quote");
1657 ctx.tx, host_data, in.snp_security_policy);
1660 ctx.tx, in.snp_uvm_endorsements, recovering);
1665 ctx.tx, attestation);
1674 std::optional<ccf::ClaimsDigest::Digest> digest =
1676 if (digest.has_value())
1678 auto digest_value = digest.value();
1679 ctx.rpc_ctx->set_claims_digest(std::move(digest_value));
1684 ctx.tx, recovering);
1693 {std::make_shared<AnyCertAuthnPolicy>()})
1694 .set_openapi_hidden(
true)
1698 auto refresh_jwt_keys = [
this](
auto& ctx, nlohmann::json&& body) {
1702 if (!primary_id.has_value())
1706 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1707 ccf::errors::InternalError,
1708 "Primary is unknown");
1711 const auto& sig_auth_ident =
1712 ctx.template get_caller<ccf::NodeCertAuthnIdentity>();
1713 if (primary_id.value() != sig_auth_ident.node_id)
1716 "JWT key auto-refresh: request does not originate from primary");
1718 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1719 ccf::errors::InternalError,
1720 "Request does not originate from primary.");
1731 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1732 ccf::errors::InternalError,
1733 "Unable to parse body.");
1736 auto issuers = ctx.tx.ro(this->network.
jwt_issuers);
1737 auto issuer_metadata_ = issuers->get(parsed.
issuer);
1738 if (!issuer_metadata_.has_value())
1741 "JWT key auto-refresh: {} is not a valid issuer", parsed.
issuer);
1743 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1744 ccf::errors::InternalError,
1745 fmt::format(
"{} is not a valid issuer.", parsed.
issuer));
1747 auto& issuer_metadata = issuer_metadata_.value();
1749 if (!issuer_metadata.auto_refresh)
1752 "JWT key auto-refresh: {} does not have auto_refresh enabled",
1755 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1756 ccf::errors::InternalError,
1758 "{} does not have auto_refresh enabled.", parsed.
issuer));
1761 if (!set_jwt_public_signing_keys(
1769 "JWT key auto-refresh: error while storing signing keys for issuer "
1773 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1774 ccf::errors::InternalError,
1776 "Error while storing signing keys for issuer {}.",
1783 "/jwt_keys/refresh",
1786 {std::make_shared<NodeCertAuthnPolicy>()})
1787 .set_openapi_hidden(
true)
1790 auto get_jwt_metrics =
1791 [
this](
auto& ,
const nlohmann::json& ) {
1794 make_read_only_endpoint(
1795 "/jwt_keys/refresh/metrics",
1802 auto service_config_handler =
1803 [
this](
auto& args,
const nlohmann::json& ) {
1807 "/service/configuration",
1815 auto list_indexing_strategies = [
this](
1817 const nlohmann::json& ) {
1818 return make_success(this->context.get_indexing_strategies().describe());
1822 "/index/strategies",
1827 .set_auto_schema<void, nlohmann::json>()
1830 auto get_ready_app =
1832 auto node_configuration_subsystem =
1834 if (!node_configuration_subsystem)
1836 ctx.rpc_ctx->set_error(
1837 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1838 ccf::errors::InternalError,
1839 "NodeConfigurationSubsystem is not available");
1843 !node_configuration_subsystem->has_received_stop_notice() &&
1847 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1851 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1855 make_read_only_endpoint(
1856 "/ready/app", HTTP_GET, get_ready_app, no_auth_required)
1857 .set_auto_schema<void,
void>()
1861 auto get_ready_gov =
1863 auto node_configuration_subsystem =
1865 if (!node_configuration_subsystem)
1867 ctx.rpc_ctx->set_error(
1868 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1869 ccf::errors::InternalError,
1870 "NodeConfigurationSubsystem is not available");
1874 !node_configuration_subsystem->has_received_stop_notice() &&
1878 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1882 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1886 make_read_only_endpoint(
1887 "/ready/gov", HTTP_GET, get_ready_gov, no_auth_required)
1888 .set_auto_schema<void,
void>()
1892 auto create_snapshot = [
this](
auto& args, nlohmann::json&&) {
1893 auto* snapshot_create = args.tx.template rw<ccf::SnapshotCreate>(
1894 ccf::Tables::SNAPSHOT_CREATE);
1895 snapshot_create->touch();
1904 .set_auto_schema<void,
void>()
1909 ccf::node::init_recovery_decision_protocol_handlers(*
this, context);
1911 ccf::node::init_file_serving_handlers(*
this, context);
1913 auto historical_cache_info = [
this](
1914 [[maybe_unused]]
auto& args,
1915 [[maybe_unused]] nlohmann::json&&) {
1918 this->context.get_historical_state().get_estimated_store_cache_size();
1921 make_read_only_endpoint(
1922 "/historical_cache",