CCF
Loading...
Searching...
No Matches
node_frontend.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
7#include "ccf/http_query.h"
9#include "ccf/json_handler.h"
10#include "ccf/node/quote.h"
11#include "ccf/odata_error.h"
12#include "ccf/pal/attestation.h"
13#include "ccf/pal/mem.h"
15#include "ccf/version.h"
16#include "crypto/certs.h"
17#include "crypto/csr.h"
18#include "ds/files.h"
19#include "ds/std_formatters.h"
20#include "frontend.h"
21#include "node/network_state.h"
26#include "node_interface.h"
29#include "snapshots/filenames.h"
30
31namespace ccf
32{
33 struct Quote
34 {
36 std::vector<uint8_t> raw;
37 std::vector<uint8_t> endorsements;
39
40 std::string measurement = {}; // < Hex-encoded
41
42 std::optional<std::vector<uint8_t>> uvm_endorsements =
43 std::nullopt; // SNP only
44 };
45
47 DECLARE_JSON_REQUIRED_FIELDS(Quote, node_id, raw, endorsements, format);
48 DECLARE_JSON_OPTIONAL_FIELDS(Quote, measurement, uvm_endorsements);
49
50 struct Attestation : public Quote
51 {};
54
55 struct GetQuotes
56 {
57 using In = void;
58
59 struct Out
60 {
61 std::vector<Quote> quotes;
62 };
63 };
64
67
69 {
70 using In = void;
71
72 struct Out
73 {
74 std::vector<Attestation> attestations;
75 };
76 };
77
80
85
88
98
102 bytecode_size,
103 bytecode_used,
104 max_heap_size,
105 max_stack_size,
106 max_execution_time,
107 max_cached_interpreters);
108
110 {
111 size_t attempts = 0;
112 size_t successes = 0;
113 size_t failures = 0;
114 };
115
117 DECLARE_JSON_REQUIRED_FIELDS(JWTRefreshMetrics, attempts, successes, failures)
118
120 {
121 std::string issuer;
123 };
124
127
129 {
130 std::string address;
131 };
132
135
136 using ConsensusConfig = std::map<std::string, ConsensusNodeConfig>;
137
142
145
150
153 SelfSignedNodeCertificateInfo, self_signed_certificate);
154
162
165 GetServicePreviousIdentity::Out, previous_service_identity);
166
168 {
169 public:
170 // The node frontend is exempt from backpressure rules to enable an operator
171 // to access a node that is not making progress.
173 {
174 return false;
175 }
176
177 private:
178 NetworkState& network;
179 ccf::AbstractNodeOperation& node_operation;
180
181 static std::pair<http_status, std::string> quote_verification_error(
183 {
184 switch (result)
185 {
187 return std::make_pair(
188 HTTP_STATUS_UNAUTHORIZED, "Quote could not be verified");
190 return std::make_pair(
191 HTTP_STATUS_UNAUTHORIZED,
192 "Quote does not contain known enclave measurement");
194 return std::make_pair(
195 HTTP_STATUS_UNAUTHORIZED,
196 "Quote report data does not contain node's public key hash");
198 return std::make_pair(
199 HTTP_STATUS_UNAUTHORIZED,
200 "Quote does not contain trusted host data");
202 return std::make_pair(
203 HTTP_STATUS_UNAUTHORIZED, "Quote host data is not authorised");
205 return std::make_pair(
206 HTTP_STATUS_UNAUTHORIZED, "UVM endorsements are not authorised");
207 default:
208 return std::make_pair(
209 HTTP_STATUS_INTERNAL_SERVER_ERROR,
210 "Unknown quote verification error");
211 }
212 }
213
214 struct ExistingNodeInfo
215 {
216 NodeId node_id;
217 std::optional<ccf::kv::Version> ledger_secret_seqno = std::nullopt;
218 std::optional<ccf::crypto::Pem> endorsed_certificate = std::nullopt;
219 };
220
221 std::optional<ExistingNodeInfo> check_node_exists(
222 ccf::kv::Tx& tx,
223 const std::vector<uint8_t>& self_signed_node_der,
224 std::optional<NodeStatus> node_status = std::nullopt)
225 {
226 // Check that a node exists by looking up its public key in the nodes
227 // table.
228 auto nodes = tx.ro(network.nodes);
229 auto endorsed_node_certificates =
230 tx.ro(network.node_endorsed_certificates);
231
233 "Check node exists with certificate [{}]", self_signed_node_der);
234 auto pk_pem = ccf::crypto::public_key_pem_from_cert(self_signed_node_der);
235
236 std::optional<ExistingNodeInfo> existing_node_info = std::nullopt;
237 nodes->foreach([&existing_node_info,
238 &pk_pem,
239 &node_status,
240 &endorsed_node_certificates](
241 const NodeId& nid, const NodeInfo& ni) {
242 if (
243 ni.public_key == pk_pem &&
244 (!node_status.has_value() || ni.status == node_status.value()))
245 {
246 existing_node_info = {
247 nid, ni.ledger_secret_seqno, endorsed_node_certificates->get(nid)};
248 return false;
249 }
250 return true;
251 });
252
253 return existing_node_info;
254 }
255
256 std::optional<NodeId> check_conflicting_node_network(
257 ccf::kv::Tx& tx, const NodeInfoNetwork& node_info_network)
258 {
259 auto nodes = tx.rw(network.nodes);
260
261 std::optional<NodeId> duplicate_node_id = std::nullopt;
262 nodes->foreach([&node_info_network, &duplicate_node_id](
263 const NodeId& nid, const NodeInfo& ni) {
264 if (
265 node_info_network.node_to_node_interface.published_address ==
266 ni.node_to_node_interface.published_address &&
267 ni.status != NodeStatus::RETIRED)
268 {
269 duplicate_node_id = nid;
270 return false;
271 }
272 return true;
273 });
274
275 return duplicate_node_id;
276 }
277
278 bool is_taking_part_in_acking(NodeStatus node_status)
279 {
280 return node_status == NodeStatus::TRUSTED;
281 }
282
283 auto add_node(
284 ccf::kv::Tx& tx,
285 const std::vector<uint8_t>& node_der,
286 const JoinNetworkNodeToNode::In& in,
287 NodeStatus node_status,
288 ServiceStatus service_status,
289 ccf::ReconfigurationType reconfiguration_type)
290 {
291 auto nodes = tx.rw(network.nodes);
292 auto node_endorsed_certificates =
293 tx.rw(network.node_endorsed_certificates);
294
295 auto conflicting_node_id =
296 check_conflicting_node_network(tx, in.node_info_network);
297 if (conflicting_node_id.has_value())
298 {
299 return make_error(
300 HTTP_STATUS_BAD_REQUEST,
301 ccf::errors::NodeAlreadyExists,
302 fmt::format(
303 "A node with the same published node address {} already exists "
304 "(node id: {}).",
305 in.node_info_network.node_to_node_interface.published_address,
306 conflicting_node_id.value()));
307 }
308
309 auto pubk_der = ccf::crypto::public_key_der_from_cert(node_der);
310 NodeId joining_node_id = compute_node_id_from_pubk_der(pubk_der);
311
312 pal::PlatformAttestationMeasurement measurement;
313
314 QuoteVerificationResult verify_result = this->node_operation.verify_quote(
315 tx, in.quote_info, pubk_der, measurement);
316 if (verify_result != QuoteVerificationResult::Verified)
317 {
318 const auto [code, message] = quote_verification_error(verify_result);
319 return make_error(code, ccf::errors::InvalidQuote, message);
320 }
321
322 std::optional<ccf::kv::Version> ledger_secret_seqno = std::nullopt;
323 if (node_status == NodeStatus::TRUSTED)
324 {
325 ledger_secret_seqno =
326 this->network.ledger_secrets->get_latest(tx).first;
327 }
328
329 // Note: All new nodes should specify a CSR from 2.x
330 auto client_public_key_pem =
332 if (in.certificate_signing_request.has_value())
333 {
334 // Verify that client's public key matches the one specified in the CSR
335 auto csr_public_key_pem = ccf::crypto::public_key_pem_from_csr(
336 in.certificate_signing_request.value());
337 if (client_public_key_pem != csr_public_key_pem)
338 {
339 return make_error(
340 HTTP_STATUS_BAD_REQUEST,
341 ccf::errors::CSRPublicKeyInvalid,
342 "Public key in CSR does not match TLS client identity.");
343 }
344 }
345
346 NodeInfo node_info = {
347 in.node_info_network,
348 in.quote_info,
349 in.public_encryption_key,
350 node_status,
351 ledger_secret_seqno,
352 measurement.hex_str(),
353 in.certificate_signing_request,
354 client_public_key_pem,
355 in.node_data};
356
357 nodes->put(joining_node_id, node_info);
358
359 LOG_INFO_FMT("Node {} added as {}", joining_node_id, node_status);
360
361 JoinNetworkNodeToNode::Out rep;
362 rep.node_status = node_status;
363 rep.node_id = joining_node_id;
364
365 if (node_status == NodeStatus::TRUSTED)
366 {
367 // Joining node only submit a CSR from 2.x
368 std::optional<ccf::crypto::Pem> endorsed_certificate = std::nullopt;
369 if (in.certificate_signing_request.has_value())
370 {
371 // For a pre-open service, extract the validity period of self-signed
372 // node certificate and use it verbatim in endorsed certificate
373 auto [valid_from, valid_to] =
374 ccf::crypto::make_verifier(node_der)->validity_period();
375 endorsed_certificate = ccf::crypto::create_endorsed_cert(
376 in.certificate_signing_request.value(),
377 valid_from,
378 valid_to,
379 this->network.identity->priv_key,
380 this->network.identity->cert);
381
382 node_endorsed_certificates->put(
383 joining_node_id, {endorsed_certificate.value()});
384 }
385
386 rep.network_info = JoinNetworkNodeToNode::Out::NetworkInfo{
387 node_operation.is_part_of_public_network(),
388 node_operation.get_last_recovered_signed_idx(),
389 this->network.ledger_secrets->get(tx),
390 *this->network.identity.get(),
391 service_status,
392 endorsed_certificate,
393 node_operation.get_cose_signatures_config()};
394 }
395 return make_success(rep);
396 }
397
398 JWTRefreshMetrics jwt_refresh_metrics;
399 void handle_event_request_completed(
400 const ccf::endpoints::RequestCompletedEvent& event) override
401 {
402 if (event.method == "POST" && event.dispatch_path == "/jwt_keys/refresh")
403 {
404 jwt_refresh_metrics.attempts += 1;
405 int status_category = event.status / 100;
406 if (status_category >= 4)
407 {
408 jwt_refresh_metrics.failures += 1;
409 }
410 else if (status_category == 2)
411 {
412 jwt_refresh_metrics.successes += 1;
413 }
414 }
415 }
416
417 public:
420 network(network_),
421 node_operation(*context_.get_subsystem<ccf::AbstractNodeOperation>())
422 {
423 openapi_info.title = "CCF Public Node API";
424 openapi_info.description =
425 "This API provides public, uncredentialed access to service and node "
426 "state.";
427 openapi_info.document_version = "4.13.0";
428 }
429
430 void init_handlers() override
431 {
433
434 auto accept = [this](auto& args, const nlohmann::json& params) {
435 const auto in = params.get<JoinNetworkNodeToNode::In>();
436
437 if (
438 !this->node_operation.is_part_of_network() &&
439 !this->node_operation.is_part_of_public_network() &&
440 !this->node_operation.is_reading_private_ledger())
441 {
442 return make_error(
443 HTTP_STATUS_INTERNAL_SERVER_ERROR,
444 ccf::errors::InternalError,
445 "Target node should be part of network to accept new nodes.");
446 }
447
448 // Make sure that the joiner's snapshot is more recent than this node's
449 // snapshot. Otherwise, the joiner may not be given all the ledger
450 // secrets required to replay historical transactions.
451 auto this_startup_seqno =
452 this->node_operation.get_startup_snapshot_seqno();
453 if (
454 in.startup_seqno.has_value() &&
455 this_startup_seqno > in.startup_seqno.value())
456 {
457 return make_error(
458 HTTP_STATUS_BAD_REQUEST,
459 ccf::errors::StartupSeqnoIsOld,
460 fmt::format(
461 "Node requested to join from seqno {} which is older than this "
462 "node startup seqno {}. A snapshot at least as recent as {} must "
463 "be used instead.",
464 in.startup_seqno.value(),
465 this_startup_seqno,
466 this_startup_seqno));
467 }
468
469 auto nodes = args.tx.rw(this->network.nodes);
470 auto service = args.tx.rw(this->network.service);
471
472 auto active_service = service->get();
473 if (!active_service.has_value())
474 {
475 return make_error(
476 HTTP_STATUS_INTERNAL_SERVER_ERROR,
477 ccf::errors::InternalError,
478 "No service is available to accept new node.");
479 }
480
481 auto config = args.tx.ro(network.config);
482 auto service_config = config->get();
483
484 if (
485 active_service->status == ServiceStatus::OPENING ||
486 active_service->status == ServiceStatus::RECOVERING)
487 {
488 // If the service is opening, new nodes are trusted straight away
489 NodeStatus joining_node_status = NodeStatus::TRUSTED;
490
491 // If the node is already trusted, return network secrets
492 auto existing_node_info = check_node_exists(
493 args.tx,
494 args.rpc_ctx->get_session_context()->caller_cert,
495 joining_node_status);
496 if (existing_node_info.has_value())
497 {
499 rep.node_status = joining_node_status;
501 node_operation.is_part_of_public_network(),
502 node_operation.get_last_recovered_signed_idx(),
503 this->network.ledger_secrets->get(
504 args.tx, existing_node_info->ledger_secret_seqno),
505 *this->network.identity.get(),
506 active_service->status,
507 existing_node_info->endorsed_certificate,
508 node_operation.get_cose_signatures_config());
509
510 return make_success(rep);
511 }
512
513 if (consensus != nullptr && !this->node_operation.can_replicate())
514 {
515 auto primary_id = consensus->primary();
516 if (primary_id.has_value())
517 {
518 auto info = nodes->get(primary_id.value());
519 if (info)
520 {
521 auto& interface_id =
522 args.rpc_ctx->get_session_context()->interface_id;
523 if (!interface_id.has_value())
524 {
525 return make_error(
526 HTTP_STATUS_INTERNAL_SERVER_ERROR,
527 ccf::errors::InternalError,
528 "Cannot redirect non-RPC request.");
529 }
530 const auto& address =
531 info->rpc_interfaces[interface_id.value()].published_address;
532 args.rpc_ctx->set_response_header(
533 http::headers::LOCATION,
534 fmt::format("https://{}/node/join", address));
535
536 return make_error(
537 HTTP_STATUS_PERMANENT_REDIRECT,
538 ccf::errors::NodeCannotHandleRequest,
539 "Node is not primary; cannot handle write");
540 }
541 }
542
543 return make_error(
544 HTTP_STATUS_INTERNAL_SERVER_ERROR,
545 ccf::errors::InternalError,
546 "Primary unknown");
547 }
548
549 return add_node(
550 args.tx,
551 args.rpc_ctx->get_session_context()->caller_cert,
552 in,
553 joining_node_status,
554 active_service->status,
556 }
557
558 // If the service is open, new nodes are first added as pending and
559 // then only trusted via member governance. It is expected that a new
560 // node polls the network to retrieve the network secrets until it is
561 // trusted
562
563 auto existing_node_info = check_node_exists(
564 args.tx, args.rpc_ctx->get_session_context()->caller_cert);
565 if (existing_node_info.has_value())
566 {
568
569 // If the node already exists, return network secrets if is already
570 // trusted. Otherwise, only return its status
571 auto node_info = nodes->get(existing_node_info->node_id);
572 auto node_status = node_info->status;
573 rep.node_status = node_status;
574 rep.node_id = existing_node_info->node_id;
575 if (is_taking_part_in_acking(node_status))
576 {
578 node_operation.is_part_of_public_network(),
579 node_operation.get_last_recovered_signed_idx(),
580 this->network.ledger_secrets->get(
581 args.tx, existing_node_info->ledger_secret_seqno),
582 *this->network.identity.get(),
583 active_service->status,
584 existing_node_info->endorsed_certificate,
585 node_operation.get_cose_signatures_config());
586
587 return make_success(rep);
588 }
589 else if (node_status == NodeStatus::PENDING)
590 {
591 // Only return node status and ID
592 return make_success(rep);
593 }
594 else
595 {
596 return make_error(
597 HTTP_STATUS_BAD_REQUEST,
598 ccf::errors::InvalidNodeState,
599 fmt::format(
600 "Joining node is not in expected state ({}).", node_status));
601 }
602 }
603 else
604 {
605 if (consensus != nullptr && !this->node_operation.can_replicate())
606 {
607 auto primary_id = consensus->primary();
608 if (primary_id.has_value())
609 {
610 auto info = nodes->get(primary_id.value());
611 if (info)
612 {
613 auto& interface_id =
614 args.rpc_ctx->get_session_context()->interface_id;
615 if (!interface_id.has_value())
616 {
617 return make_error(
618 HTTP_STATUS_INTERNAL_SERVER_ERROR,
619 ccf::errors::InternalError,
620 "Cannot redirect non-RPC request.");
621 }
622 const auto& address =
623 info->rpc_interfaces[interface_id.value()].published_address;
624 args.rpc_ctx->set_response_header(
625 http::headers::LOCATION,
626 fmt::format("https://{}/node/join", address));
627
628 return make_error(
629 HTTP_STATUS_PERMANENT_REDIRECT,
630 ccf::errors::NodeCannotHandleRequest,
631 "Node is not primary; cannot handle write");
632 }
633 }
634
635 return make_error(
636 HTTP_STATUS_INTERNAL_SERVER_ERROR,
637 ccf::errors::InternalError,
638 "Primary unknown");
639 }
640
641 // If the node does not exist, add it to the KV in state pending
642 return add_node(
643 args.tx,
644 args.rpc_ctx->get_session_context()->caller_cert,
645 in,
647 active_service->status,
649 }
650 };
651 make_endpoint("/join", HTTP_POST, json_adapter(accept), no_auth_required)
652 .set_forwarding_required(endpoints::ForwardingRequired::Never)
653 .set_openapi_hidden(true)
654 .install();
655
656 auto set_retired_committed = [this](auto& ctx, nlohmann::json&&) {
657 auto nodes = ctx.tx.rw(network.nodes);
658 nodes->foreach([this, &nodes](const auto& node_id, auto node_info) {
659 auto gc_node = nodes->get_globally_committed(node_id);
660 if (
661 gc_node.has_value() &&
662 gc_node->status == ccf::NodeStatus::RETIRED &&
663 !node_info.retired_committed)
664 {
665 // Set retired_committed on nodes for which RETIRED status
666 // has been committed.
667 node_info.retired_committed = true;
668 nodes->put(node_id, node_info);
669
670 LOG_DEBUG_FMT("Setting retired_committed on node {}", node_id);
671 }
672 return true;
673 });
674
675 return make_success();
676 };
677 make_endpoint(
678 "network/nodes/set_retired_committed",
679 HTTP_POST,
680 json_adapter(set_retired_committed),
681 {std::make_shared<NodeCertAuthnPolicy>()})
682 .set_openapi_hidden(true)
683 .install();
684
685 auto get_state = [this](auto& args, nlohmann::json&&) {
686 GetState::Out result;
687 auto [s, rts, lrs] = this->node_operation.state();
688 result.node_id = this->context.get_node_id();
689 result.state = s;
690 result.recovery_target_seqno = rts;
691 result.last_recovered_seqno = lrs;
692 result.startup_seqno =
693 this->node_operation.get_startup_snapshot_seqno();
694
695 auto signatures = args.tx.template ro<Signatures>(Tables::SIGNATURES);
696 auto sig = signatures->get();
697 if (!sig.has_value())
698 {
699 result.last_signed_seqno = 0;
700 }
701 else
702 {
703 result.last_signed_seqno = sig.value().seqno;
704 }
705
706 auto node_configuration_subsystem =
707 this->context.get_subsystem<NodeConfigurationSubsystem>();
708 if (!node_configuration_subsystem)
709 {
710 return make_error(
711 HTTP_STATUS_INTERNAL_SERVER_ERROR,
712 ccf::errors::InternalError,
713 "NodeConfigurationSubsystem is not available");
714 }
715 result.stop_notice =
716 node_configuration_subsystem->has_received_stop_notice();
717
718 return make_success(result);
719 };
720 make_read_only_endpoint(
721 "/state", HTTP_GET, json_read_only_adapter(get_state), no_auth_required)
722 .set_auto_schema<GetState>()
723 .set_forwarding_required(endpoints::ForwardingRequired::Never)
724 .install();
725
726 auto get_quote = [this](auto& args, nlohmann::json&&) {
727 QuoteInfo node_quote_info;
728 const auto result =
729 get_quote_for_this_node_v1(args.tx, node_quote_info);
730 if (result == ApiResult::OK)
731 {
732 Quote q;
733 q.node_id = context.get_node_id();
734 q.raw = node_quote_info.quote;
735 q.endorsements = node_quote_info.endorsements;
736 q.format = node_quote_info.format;
737 q.uvm_endorsements = node_quote_info.uvm_endorsements;
738
739 auto nodes = args.tx.ro(network.nodes);
740 auto node_info = nodes->get(context.get_node_id());
741 if (node_info.has_value() && node_info->code_digest.has_value())
742 {
743 q.measurement = node_info->code_digest.value();
744 }
745 else
746 {
747 auto measurement =
749 if (measurement.has_value())
750 {
751 q.measurement = measurement.value().hex_str();
752 }
753 else
754 {
755 return make_error(
756 HTTP_STATUS_INTERNAL_SERVER_ERROR,
757 ccf::errors::InvalidQuote,
758 "Failed to extract code id from node quote.");
759 }
760 }
761
762 return make_success(q);
763 }
764 else if (result == ApiResult::NotFound)
765 {
766 return make_error(
767 HTTP_STATUS_NOT_FOUND,
768 ccf::errors::ResourceNotFound,
769 "Could not find node quote.");
770 }
771 else
772 {
773 return make_error(
774 HTTP_STATUS_INTERNAL_SERVER_ERROR,
775 ccf::errors::InternalError,
776 fmt::format("Error code: {}", ccf::api_result_to_str(result)));
777 }
778 };
779 make_read_only_endpoint(
780 "/quotes/self",
781 HTTP_GET,
782 json_read_only_adapter(get_quote),
783 no_auth_required)
784 .set_auto_schema<void, Quote>()
785 .set_forwarding_required(endpoints::ForwardingRequired::Never)
786 .install();
787 make_read_only_endpoint(
788 "/attestations/self",
789 HTTP_GET,
790 json_read_only_adapter(get_quote),
791 no_auth_required)
792 .set_auto_schema<void, Attestation>()
793 .set_forwarding_required(endpoints::ForwardingRequired::Never)
794 .install();
795
796 auto get_quotes = [this](auto& args, nlohmann::json&&) {
797 GetQuotes::Out result;
798
799 auto nodes = args.tx.ro(network.nodes);
800 nodes->foreach([&quotes = result.quotes](
801 const auto& node_id, const auto& node_info) {
802 if (node_info.status == ccf::NodeStatus::TRUSTED)
803 {
804 Quote q;
805 q.node_id = node_id;
806 q.raw = node_info.quote_info.quote;
807 q.endorsements = node_info.quote_info.endorsements;
808 q.format = node_info.quote_info.format;
809 q.uvm_endorsements = node_info.quote_info.uvm_endorsements;
810
811 if (node_info.code_digest.has_value())
812 {
813 q.measurement = node_info.code_digest.value();
814 }
815 else
816 {
817 auto measurement =
818 AttestationProvider::get_measurement(node_info.quote_info);
819 if (measurement.has_value())
820 {
821 q.measurement = measurement.value().hex_str();
822 }
823 }
824 quotes.emplace_back(q);
825 }
826 return true;
827 });
828
829 return make_success(result);
830 };
831 make_read_only_endpoint(
832 "/quotes",
833 HTTP_GET,
834 json_read_only_adapter(get_quotes),
835 no_auth_required)
836 .set_auto_schema<GetQuotes>()
837 .install();
838
839 auto get_attestations =
840 [this, get_quotes](auto& args, nlohmann::json&& params) {
841 const auto res = get_quotes(args, std::move(params));
842 const auto body = std::get_if<nlohmann::json>(&res);
843 if (body != nullptr)
844 {
845 auto result = nlohmann::json::object();
846 result["attestations"] = (*body)["quotes"];
847 return make_success(result);
848 }
849
850 return res;
851 };
852 make_read_only_endpoint(
853 "/attestations",
854 HTTP_GET,
855 json_read_only_adapter(get_attestations),
856 no_auth_required)
857 .set_auto_schema<GetAttestations>()
858 .install();
859
860 auto network_status = [this](auto& args, nlohmann::json&&) {
862 auto service = args.tx.ro(network.service);
863 auto service_state = service->get();
864 if (service_state.has_value())
865 {
866 const auto& service_value = service_state.value();
867 out.service_status = service_value.status;
868 out.service_certificate = service_value.cert;
869 out.recovery_count = service_value.recovery_count.value_or(0);
870 out.service_data = service_value.service_data;
872 service_value.current_service_create_txid;
873 if (consensus != nullptr)
874 {
875 out.current_view = consensus->get_view();
876 auto primary_id = consensus->primary();
877 if (primary_id.has_value())
878 {
879 out.primary_id = primary_id.value();
880 }
881 }
882 return make_success(out);
883 }
884 return make_error(
885 HTTP_STATUS_NOT_FOUND,
886 ccf::errors::ResourceNotFound,
887 "Service state not available.");
888 };
889 make_read_only_endpoint(
890 "/network",
891 HTTP_GET,
892 json_read_only_adapter(network_status),
893 no_auth_required)
894 .set_auto_schema<void, GetNetworkInfo::Out>()
895 .install();
896
897 auto service_previous_identity = [this](auto& args, nlohmann::json&&) {
898 auto psi_handle = args.tx.template ro<ccf::PreviousServiceIdentity>(
899 ccf::Tables::PREVIOUS_SERVICE_IDENTITY);
900 const auto psi = psi_handle->get();
901 if (psi.has_value())
902 {
904 out.previous_service_identity = psi.value();
905 return make_success(out);
906 }
907 else
908 {
909 return make_error(
910 HTTP_STATUS_NOT_FOUND,
911 ccf::errors::ResourceNotFound,
912 "This service is not a recovery of a previous service.");
913 }
914 };
915 make_read_only_endpoint(
916 "/service/previous_identity",
917 HTTP_GET,
918 json_read_only_adapter(service_previous_identity),
919 no_auth_required)
920 .set_auto_schema<void, GetServicePreviousIdentity::Out>()
921 .install();
922
923 auto get_nodes = [this](auto& args, nlohmann::json&&) {
924 const auto parsed_query =
925 http::parse_query(args.rpc_ctx->get_request_query());
926
927 std::string error_string; // Ignored - all params are optional
928 const auto host = http::get_query_value_opt<std::string>(
929 parsed_query, "host", error_string);
930 const auto port = http::get_query_value_opt<std::string>(
931 parsed_query, "port", error_string);
932 const auto status_str = http::get_query_value_opt<std::string>(
933 parsed_query, "status", error_string);
934
935 std::optional<NodeStatus> status;
936 if (status_str.has_value())
937 {
938 // Convert the query argument to a JSON string, try to parse it as
939 // a NodeStatus, return an error if this doesn't work
940 try
941 {
942 status = nlohmann::json(status_str.value()).get<NodeStatus>();
943 }
944 catch (const ccf::JsonParseError& e)
945 {
946 return ccf::make_error(
947 HTTP_STATUS_BAD_REQUEST,
948 ccf::errors::InvalidQueryParameterValue,
949 fmt::format(
950 "Query parameter '{}' is not a valid node status",
951 status_str.value()));
952 }
953 }
954
955 GetNodes::Out out;
956
957 auto nodes = args.tx.ro(this->network.nodes);
958 nodes->foreach([this, host, port, status, &out, nodes](
959 const NodeId& nid, const NodeInfo& ni) {
960 if (status.has_value() && status.value() != ni.status)
961 {
962 return true;
963 }
964
965 // Match on any interface
966 bool is_matched = false;
967 for (auto const& interface : ni.rpc_interfaces)
968 {
969 const auto& [pub_host, pub_port] =
970 split_net_address(interface.second.published_address);
971
972 if (
973 (!host.has_value() || host.value() == pub_host) &&
974 (!port.has_value() || port.value() == pub_port))
975 {
976 is_matched = true;
977 break;
978 }
979 }
980
981 if (!is_matched)
982 {
983 return true;
984 }
985
986 bool is_primary = false;
987 if (consensus != nullptr)
988 {
989 is_primary = consensus->primary() == nid;
990 }
991
992 out.nodes.push_back(
993 {nid,
994 ni.status,
995 is_primary,
997 ni.node_data,
998 nodes->get_version_of_previous_write(nid).value_or(0)});
999 return true;
1000 });
1001
1002 return make_success(out);
1003 };
1004 make_read_only_endpoint(
1005 "/network/nodes",
1006 HTTP_GET,
1007 json_read_only_adapter(get_nodes),
1008 no_auth_required)
1009 .set_auto_schema<void, GetNodes::Out>()
1010 .add_query_parameter<std::string>(
1012 .add_query_parameter<std::string>(
1014 .add_query_parameter<std::string>(
1016 .install();
1017
1018 auto get_removable_nodes = [this](auto& args, nlohmann::json&&) {
1019 GetNodes::Out out;
1020
1021 auto nodes = args.tx.ro(this->network.nodes);
1022 nodes->foreach(
1023 [this, &out, nodes](const NodeId& node_id, const NodeInfo& ni) {
1024 // Only nodes whose retire_committed status is committed can be
1025 // safely removed, because any primary elected from here on would
1026 // consider them retired, and would consequently not need their
1027 // input in any quorum. We must therefore read the KV at its
1028 // globally committed watermark, for the purpose of this RPC. Since
1029 // this transaction does not perform a write, it is safe to do this.
1030 auto node = nodes->get_globally_committed(node_id);
1031 if (
1032 node.has_value() && node->status == ccf::NodeStatus::RETIRED &&
1033 node->retired_committed)
1034 {
1035 out.nodes.push_back(
1036 {node_id,
1037 node->status,
1038 false /* is_primary */,
1039 node->rpc_interfaces,
1040 node->node_data,
1041 nodes->get_version_of_previous_write(node_id).value_or(0)});
1042 }
1043 return true;
1044 });
1045
1046 return make_success(out);
1047 };
1048
1049 make_read_only_endpoint(
1050 "/network/removable_nodes",
1051 HTTP_GET,
1052 json_read_only_adapter(get_removable_nodes),
1053 no_auth_required)
1054 .set_auto_schema<void, GetNodes::Out>()
1055 .install();
1056
1057 auto delete_retired_committed_node =
1058 [this](auto& args, nlohmann::json&&) {
1059 GetNodes::Out out;
1060
1061 std::string node_id;
1062 std::string error;
1063 if (!get_path_param(
1064 args.rpc_ctx->get_request_path_params(),
1065 "node_id",
1066 node_id,
1067 error))
1068 {
1069 return make_error(
1070 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName, error);
1071 }
1072
1073 auto nodes = args.tx.rw(this->network.nodes);
1074 if (!nodes->has(node_id))
1075 {
1076 return make_error(
1077 HTTP_STATUS_NOT_FOUND,
1078 ccf::errors::ResourceNotFound,
1079 "No such node");
1080 }
1081
1082 auto node_endorsed_certificates =
1083 args.tx.rw(network.node_endorsed_certificates);
1084
1085 // A node's retirement is only complete when the
1086 // transition of retired_committed is itself committed,
1087 // i.e. when the next eligible primary is guaranteed to
1088 // be aware the retirement is committed.
1089 // As a result, the handler must check node info at the
1090 // current committed level, rather than at the end of the
1091 // local suffix.
1092 // While this transaction does execute a write, it specifically
1093 // deletes the value it reads from. It is therefore safe to
1094 // execute on the basis of a potentially stale read-set,
1095 // which get_globally_committed() typically produces.
1096 auto node = nodes->get_globally_committed(node_id);
1097 if (
1098 node.has_value() && node->status == ccf::NodeStatus::RETIRED &&
1099 node->retired_committed)
1100 {
1101 nodes->remove(node_id);
1102 node_endorsed_certificates->remove(node_id);
1103 }
1104 else
1105 {
1106 return make_error(
1107 HTTP_STATUS_BAD_REQUEST,
1108 ccf::errors::NodeNotRetiredCommitted,
1109 "Node is not completely retired");
1110 }
1111
1112 return make_success(true);
1113 };
1114
1115 make_endpoint(
1116 "/network/nodes/{node_id}",
1117 HTTP_DELETE,
1118 json_adapter(delete_retired_committed_node),
1119 no_auth_required)
1120 .set_auto_schema<void, bool>()
1121 .install();
1122
1123 auto get_self_signed_certificate = [this](auto& args, nlohmann::json&&) {
1125 this->node_operation.get_self_signed_node_certificate()};
1126 };
1127 make_command_endpoint(
1128 "/self_signed_certificate",
1129 HTTP_GET,
1130 json_command_adapter(get_self_signed_certificate),
1131 no_auth_required)
1132 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1133 .set_auto_schema<void, SelfSignedNodeCertificateInfo>()
1134 .install();
1135
1136 auto get_node_info = [this](auto& args, nlohmann::json&&) {
1137 std::string node_id;
1138 std::string error;
1139 if (!get_path_param(
1140 args.rpc_ctx->get_request_path_params(),
1141 "node_id",
1142 node_id,
1143 error))
1144 {
1145 return make_error(
1146 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName, error);
1147 }
1148
1149 auto nodes = args.tx.ro(this->network.nodes);
1150 auto info = nodes->get(node_id);
1151
1152 if (!info)
1153 {
1154 return make_error(
1155 HTTP_STATUS_NOT_FOUND,
1156 ccf::errors::ResourceNotFound,
1157 "Node not found");
1158 }
1159
1160 bool is_primary = false;
1161 if (consensus != nullptr)
1162 {
1163 auto primary = consensus->primary();
1164 if (primary.has_value() && primary.value() == node_id)
1165 {
1166 is_primary = true;
1167 }
1168 }
1169 auto& ni = info.value();
1171 node_id,
1172 ni.status,
1173 is_primary,
1174 ni.rpc_interfaces,
1175 ni.node_data,
1176 nodes->get_version_of_previous_write(node_id).value_or(0)});
1177 };
1178 make_read_only_endpoint(
1179 "/network/nodes/{node_id}",
1180 HTTP_GET,
1181 json_read_only_adapter(get_node_info),
1182 no_auth_required)
1183 .set_auto_schema<void, GetNode::Out>()
1184 .install();
1185
1186 auto get_self_node = [this](auto& args, nlohmann::json&&) {
1187 auto node_id = this->context.get_node_id();
1188 auto nodes = args.tx.ro(this->network.nodes);
1189 auto info = nodes->get(node_id);
1190
1191 bool is_primary = false;
1192 if (consensus != nullptr)
1193 {
1194 auto primary = consensus->primary();
1195 if (primary.has_value() && primary.value() == node_id)
1196 {
1197 is_primary = true;
1198 }
1199 }
1200
1201 if (info.has_value())
1202 {
1203 // Answers from the KV are preferred, as they are more up-to-date,
1204 // especially status and node_data.
1205 auto& ni = info.value();
1207 node_id,
1208 ni.status,
1209 is_primary,
1210 ni.rpc_interfaces,
1211 ni.node_data,
1212 nodes->get_version_of_previous_write(node_id).value_or(0)});
1213 }
1214 else
1215 {
1216 // If the node isn't in its KV yet, fall back to configuration
1217 auto node_configuration_subsystem =
1218 this->context.get_subsystem<NodeConfigurationSubsystem>();
1219 if (!node_configuration_subsystem)
1220 {
1221 return make_error(
1222 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1223 ccf::errors::InternalError,
1224 "NodeConfigurationSubsystem is not available");
1225 }
1226 const auto& node_startup_config =
1227 node_configuration_subsystem->get().node_config;
1229 node_id,
1231 is_primary,
1232 node_startup_config.network.rpc_interfaces,
1233 node_startup_config.node_data,
1234 0});
1235 }
1236 };
1237 make_read_only_endpoint(
1238 "/network/nodes/self",
1239 HTTP_GET,
1240 json_read_only_adapter(get_self_node),
1241 no_auth_required)
1242 .set_auto_schema<void, GetNode::Out>()
1243 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1244 .install();
1245
1246 auto get_primary_node = [this](auto& args, nlohmann::json&&) {
1247 if (consensus != nullptr)
1248 {
1249 auto primary_id = consensus->primary();
1250 if (!primary_id.has_value())
1251 {
1252 return make_error(
1253 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1254 ccf::errors::InternalError,
1255 "Primary unknown");
1256 }
1257
1258 auto nodes = args.tx.ro(this->network.nodes);
1259 auto info = nodes->get(primary_id.value());
1260 if (!info)
1261 {
1262 return make_error(
1263 HTTP_STATUS_NOT_FOUND,
1264 ccf::errors::ResourceNotFound,
1265 "Node not found");
1266 }
1267
1268 auto& ni = info.value();
1270 primary_id.value(),
1271 ni.status,
1272 true,
1273 ni.rpc_interfaces,
1274 ni.node_data,
1275 nodes->get_version_of_previous_write(primary_id.value())
1276 .value_or(0)});
1277 }
1278 else
1279 {
1280 return make_error(
1281 HTTP_STATUS_NOT_FOUND,
1282 ccf::errors::ResourceNotFound,
1283 "No configured consensus");
1284 }
1285 };
1286 make_read_only_endpoint(
1287 "/network/nodes/primary",
1288 HTTP_GET,
1289 json_read_only_adapter(get_primary_node),
1290 no_auth_required)
1291 .set_auto_schema<void, GetNode::Out>()
1292 .install();
1293
1294 auto head_primary = [this](auto& args) {
1295 if (this->node_operation.can_replicate())
1296 {
1297 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1298 }
1299 else
1300 {
1301 args.rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT);
1302 if (consensus != nullptr)
1303 {
1304 auto primary_id = consensus->primary();
1305 if (!primary_id.has_value())
1306 {
1307 args.rpc_ctx->set_error(
1308 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1309 ccf::errors::InternalError,
1310 "Primary unknown");
1311 return;
1312 }
1313
1314 auto nodes = args.tx.ro(this->network.nodes);
1315 auto info = nodes->get(primary_id.value());
1316 if (info)
1317 {
1318 auto& interface_id =
1319 args.rpc_ctx->get_session_context()->interface_id;
1320 if (!interface_id.has_value())
1321 {
1322 args.rpc_ctx->set_error(
1323 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1324 ccf::errors::InternalError,
1325 "Cannot redirect non-RPC request.");
1326 return;
1327 }
1328 const auto& address =
1329 info->rpc_interfaces[interface_id.value()].published_address;
1330 args.rpc_ctx->set_response_header(
1331 http::headers::LOCATION,
1332 fmt::format("https://{}/node/primary", address));
1333 }
1334 }
1335 }
1336 };
1337 make_read_only_endpoint(
1338 "/primary", HTTP_HEAD, head_primary, no_auth_required)
1339 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1340 .install();
1341
1342 auto get_primary = [this](auto& args) {
1343 if (this->node_operation.can_replicate())
1344 {
1345 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1346 return;
1347 }
1348 else
1349 {
1350 args.rpc_ctx->set_error(
1351 HTTP_STATUS_NOT_FOUND,
1352 ccf::errors::ResourceNotFound,
1353 "Node is not primary");
1354 return;
1355 }
1356 };
1357 make_read_only_endpoint(
1358 "/primary", HTTP_GET, get_primary, no_auth_required)
1359 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1360 .install();
1361
1362 auto get_backup = [this](auto& args) {
1363 if (!this->node_operation.can_replicate())
1364 {
1365 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1366 return;
1367 }
1368 else
1369 {
1370 args.rpc_ctx->set_error(
1371 HTTP_STATUS_NOT_FOUND,
1372 ccf::errors::ResourceNotFound,
1373 "Node is not backup");
1374 return;
1375 }
1376 };
1377 make_read_only_endpoint("/backup", HTTP_GET, get_backup, no_auth_required)
1378 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1379 .install();
1380
1381 auto consensus_config = [this](auto& args, nlohmann::json&&) {
1382 // Query node for configurations, separate current from pending
1383 if (consensus != nullptr)
1384 {
1385 auto cfg = consensus->get_latest_configuration();
1386 ConsensusConfig cc;
1387 for (auto& [nid, ninfo] : cfg)
1388 {
1389 cc.emplace(
1390 nid.value(),
1392 fmt::format("{}:{}", ninfo.hostname, ninfo.port)});
1393 }
1394 return make_success(cc);
1395 }
1396 else
1397 {
1398 return make_error(
1399 HTTP_STATUS_NOT_FOUND,
1400 ccf::errors::ResourceNotFound,
1401 "No configured consensus");
1402 }
1403 };
1404
1405 make_command_endpoint(
1406 "/config",
1407 HTTP_GET,
1408 json_command_adapter(consensus_config),
1409 no_auth_required)
1410 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1411 .set_auto_schema<void, ConsensusConfig>()
1412 .install();
1413
1414 auto consensus_state = [this](auto& args, nlohmann::json&&) {
1415 if (consensus != nullptr)
1416 {
1417 return make_success(ConsensusConfigDetails{consensus->get_details()});
1418 }
1419 else
1420 {
1421 return make_error(
1422 HTTP_STATUS_NOT_FOUND,
1423 ccf::errors::ResourceNotFound,
1424 "No configured consensus");
1425 }
1426 };
1427
1428 make_command_endpoint(
1429 "/consensus",
1430 HTTP_GET,
1431 json_command_adapter(consensus_state),
1432 no_auth_required)
1433 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1434 .set_auto_schema<void, ConsensusConfigDetails>()
1435 .install();
1436
1437 auto memory_usage = [](auto& args) {
1439 if (ccf::pal::get_mallinfo(info))
1440 {
1441 MemoryUsage::Out mu(info);
1442 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1443 args.rpc_ctx->set_response_header(
1444 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1445 args.rpc_ctx->set_response_body(nlohmann::json(mu).dump());
1446 return;
1447 }
1448
1449 args.rpc_ctx->set_response_status(HTTP_STATUS_INTERNAL_SERVER_ERROR);
1450 args.rpc_ctx->set_response_body("Failed to read memory usage");
1451 };
1452
1453 make_command_endpoint("/memory", HTTP_GET, memory_usage, no_auth_required)
1454 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1455 .set_auto_schema<MemoryUsage>()
1456 .install();
1457
1458 auto node_metrics = [this](auto& args) {
1459 NodeMetrics nm;
1460 nm.sessions = node_operation.get_session_metrics();
1461
1462 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1463 args.rpc_ctx->set_response_header(
1464 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1465 args.rpc_ctx->set_response_body(nlohmann::json(nm).dump());
1466 };
1467
1468 make_command_endpoint(
1469 "/metrics", HTTP_GET, node_metrics, no_auth_required)
1470 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1471 .set_auto_schema<void, NodeMetrics>()
1472 .install();
1473
1474 auto js_metrics = [this](auto& args, nlohmann::json&&) {
1475 auto bytecode_map = args.tx.ro(this->network.modules_quickjs_bytecode);
1476 auto version_val = args.tx.ro(this->network.modules_quickjs_version);
1477 uint64_t bytecode_size = 0;
1478 bytecode_map->foreach(
1479 [&bytecode_size](const auto&, const auto& bytecode) {
1480 bytecode_size += bytecode.size();
1481 return true;
1482 });
1483 auto js_engine_map = args.tx.ro(this->network.js_engine);
1485 m.bytecode_size = bytecode_size;
1486 m.bytecode_used =
1487 version_val->get() == std::string(ccf::quickjs_version);
1488
1489 auto options = js_engine_map->get().value_or(ccf::JSRuntimeOptions{});
1490 m.max_stack_size = options.max_stack_bytes;
1491 m.max_heap_size = options.max_heap_bytes;
1492 m.max_execution_time = options.max_execution_time_ms;
1493 m.max_cached_interpreters = options.max_cached_interpreters;
1494
1495 return m;
1496 };
1497
1498 make_read_only_endpoint(
1499 "/js_metrics",
1500 HTTP_GET,
1501 json_read_only_adapter(js_metrics),
1502 no_auth_required)
1503 .set_auto_schema<void, JavaScriptMetrics>()
1504 .install();
1505
1506 auto version = [this](auto&, nlohmann::json&&) {
1507 GetVersion::Out result;
1508 result.ccf_version = ccf::ccf_version;
1509 result.quickjs_version = ccf::quickjs_version;
1510 result.unsafe = false;
1511
1512 return make_success(result);
1513 };
1514
1515 make_command_endpoint(
1516 "/version", HTTP_GET, json_command_adapter(version), no_auth_required)
1517 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1518 .set_auto_schema<GetVersion>()
1519 .install();
1520
1521 auto create = [this](auto& ctx, nlohmann::json&& params) {
1522 LOG_INFO_FMT("Processing create RPC");
1523
1524 bool recovering = node_operation.is_reading_public_ledger();
1525
1526 // This endpoint can only be called once, directly from the starting
1527 // node for the genesis or end of public recovery transaction to
1528 // initialise the service
1529 if (!node_operation.is_in_initialised_state() && !recovering)
1530 {
1531 return make_error(
1532 HTTP_STATUS_FORBIDDEN,
1533 ccf::errors::InternalError,
1534 "Node is not in initial state.");
1535 }
1536
1537 const auto in = params.get<CreateNetworkNodeToNode::In>();
1538
1539 if (InternalTablesAccess::is_service_created(ctx.tx, in.service_cert))
1540 {
1541 return make_error(
1542 HTTP_STATUS_FORBIDDEN,
1543 ccf::errors::InternalError,
1544 "Service is already created.");
1545 }
1546
1548 ctx.tx, in.service_cert, in.create_txid, in.service_data, recovering);
1549
1550 // Retire all nodes, in case there are any (i.e. post recovery)
1552
1553 // Genesis transaction (i.e. not after recovery)
1554 if (in.genesis_info.has_value())
1555 {
1556 // Note that it is acceptable to start a network without any member
1557 // having a recovery share. The service will check that at least one
1558 // recovery member is added before the service is opened.
1559 for (const auto& info : in.genesis_info->members)
1560 {
1562 }
1563
1565 ctx.tx, in.genesis_info->service_configuration);
1567 ctx.tx, in.genesis_info->constitution);
1568 }
1569 else
1570 {
1571 // On recovery, force a new ledger chunk
1572 auto tx_ = static_cast<ccf::kv::CommittableTx*>(&ctx.tx);
1573 if (tx_ == nullptr)
1574 {
1575 throw std::logic_error("Could not cast tx to CommittableTx");
1576 }
1577 tx_->set_tx_flag(
1579 }
1580
1581 auto endorsed_certificates =
1582 ctx.tx.rw(network.node_endorsed_certificates);
1583 endorsed_certificates->put(in.node_id, in.node_endorsed_certificate);
1584
1585 NodeInfo node_info = {
1586 in.node_info_network,
1587 {in.quote_info},
1588 in.public_encryption_key,
1590 std::nullopt,
1591 in.measurement.hex_str(),
1592 in.certificate_signing_request,
1593 in.public_key,
1594 in.node_data};
1595 InternalTablesAccess::add_node(ctx.tx, in.node_id, node_info);
1596
1597 if (
1598 in.quote_info.format != QuoteFormat::amd_sev_snp_v1 ||
1599 !in.snp_uvm_endorsements.has_value())
1600 {
1601 // For improved serviceability on SNP, do not record trusted
1602 // measurements if UVM endorsements are available
1604 ctx.tx, in.measurement, in.quote_info.format);
1605 }
1606
1607 switch (in.quote_info.format)
1608 {
1610 {
1611 auto host_data = AttestationProvider::get_host_data(in.quote_info);
1612 if (host_data.has_value())
1613 {
1615 ctx.tx, host_data.value());
1616 }
1617 else
1618 {
1619 LOG_FAIL_FMT("Unable to extract host data from virtual quote");
1620 }
1621 break;
1622 }
1623
1625 {
1626 auto host_data =
1627 AttestationProvider::get_host_data(in.quote_info).value();
1629 ctx.tx, host_data, in.snp_security_policy);
1630
1632 ctx.tx, in.snp_uvm_endorsements);
1633
1634 auto attestation =
1635 AttestationProvider::get_snp_attestation(in.quote_info).value();
1637 ctx.tx, attestation);
1638 break;
1639 }
1640
1641 default:
1642 {
1643 break;
1644 }
1645 }
1646
1647 std::optional<ccf::ClaimsDigest::Digest> digest =
1649 if (digest.has_value())
1650 {
1651 auto digest_value = digest.value();
1652 ctx.rpc_ctx->set_claims_digest(std::move(digest_value));
1653 }
1654
1655 LOG_INFO_FMT("Created service");
1656 return make_success(true);
1657 };
1658 make_endpoint(
1659 "/create", HTTP_POST, json_adapter(create), no_auth_required)
1660 .set_openapi_hidden(true)
1661 .install();
1662
1663 // Only called from node. See node_state.h.
1664 auto refresh_jwt_keys = [this](auto& ctx, nlohmann::json&& body) {
1665 // All errors are server errors since the client is the server.
1666
1667 auto primary_id = consensus->primary();
1668 if (!primary_id.has_value())
1669 {
1670 LOG_FAIL_FMT("JWT key auto-refresh: primary unknown");
1671 return make_error(
1672 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1673 ccf::errors::InternalError,
1674 "Primary is unknown");
1675 }
1676
1677 const auto& sig_auth_ident =
1678 ctx.template get_caller<ccf::NodeCertAuthnIdentity>();
1679 if (primary_id.value() != sig_auth_ident.node_id)
1680 {
1682 "JWT key auto-refresh: request does not originate from primary");
1683 return make_error(
1684 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1685 ccf::errors::InternalError,
1686 "Request does not originate from primary.");
1687 }
1688
1690 try
1691 {
1692 parsed = body.get<SetJwtPublicSigningKeys>();
1693 }
1694 catch (const ccf::JsonParseError& e)
1695 {
1696 return make_error(
1697 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1698 ccf::errors::InternalError,
1699 "Unable to parse body.");
1700 }
1701
1702 auto issuers = ctx.tx.ro(this->network.jwt_issuers);
1703 auto issuer_metadata_ = issuers->get(parsed.issuer);
1704 if (!issuer_metadata_.has_value())
1705 {
1707 "JWT key auto-refresh: {} is not a valid issuer", parsed.issuer);
1708 return make_error(
1709 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1710 ccf::errors::InternalError,
1711 fmt::format("{} is not a valid issuer.", parsed.issuer));
1712 }
1713 auto& issuer_metadata = issuer_metadata_.value();
1714
1715 if (!issuer_metadata.auto_refresh)
1716 {
1718 "JWT key auto-refresh: {} does not have auto_refresh enabled",
1719 parsed.issuer);
1720 return make_error(
1721 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1722 ccf::errors::InternalError,
1723 fmt::format(
1724 "{} does not have auto_refresh enabled.", parsed.issuer));
1725 }
1726
1727 if (!set_jwt_public_signing_keys(
1728 ctx.tx,
1729 "<auto-refresh>",
1730 parsed.issuer,
1731 issuer_metadata,
1732 parsed.jwks))
1733 {
1735 "JWT key auto-refresh: error while storing signing keys for issuer "
1736 "{}",
1737 parsed.issuer);
1738 return make_error(
1739 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1740 ccf::errors::InternalError,
1741 fmt::format(
1742 "Error while storing signing keys for issuer {}.",
1743 parsed.issuer));
1744 }
1745
1746 return make_success(true);
1747 };
1748 make_endpoint(
1749 "/jwt_keys/refresh",
1750 HTTP_POST,
1751 json_adapter(refresh_jwt_keys),
1752 {std::make_shared<NodeCertAuthnPolicy>()})
1753 .set_openapi_hidden(true)
1754 .install();
1755
1756 auto get_jwt_metrics = [this](auto& args, const nlohmann::json& params) {
1757 return make_success(jwt_refresh_metrics);
1758 };
1759 make_read_only_endpoint(
1760 "/jwt_keys/refresh/metrics",
1761 HTTP_GET,
1762 json_read_only_adapter(get_jwt_metrics),
1763 no_auth_required)
1764 .set_auto_schema<void, JWTRefreshMetrics>()
1765 .install();
1766
1767 auto service_config_handler =
1768 [this](auto& args, const nlohmann::json& params) {
1769 return make_success(args.tx.ro(network.config)->get());
1770 };
1771 make_endpoint(
1772 "/service/configuration",
1773 HTTP_GET,
1774 json_adapter(service_config_handler),
1775 no_auth_required)
1776 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1777 .set_auto_schema<void, ServiceConfiguration>()
1778 .install();
1779
1780 auto list_indexing_strategies = [this](
1781 auto& args,
1782 const nlohmann::json& params) {
1783 return make_success(this->context.get_indexing_strategies().describe());
1784 };
1785
1786 make_endpoint(
1787 "/index/strategies",
1788 HTTP_GET,
1789 json_adapter(list_indexing_strategies),
1790 no_auth_required)
1791 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1792 .set_auto_schema<void, nlohmann::json>()
1793 .install();
1794
1795 auto get_ready_app =
1796 [this](const ccf::endpoints::ReadOnlyEndpointContext& ctx) {
1797 auto node_configuration_subsystem =
1798 this->context.get_subsystem<NodeConfigurationSubsystem>();
1799 if (!node_configuration_subsystem)
1800 {
1801 ctx.rpc_ctx->set_error(
1802 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1803 ccf::errors::InternalError,
1804 "NodeConfigurationSubsystem is not available");
1805 return;
1806 }
1807 if (
1808 !node_configuration_subsystem->has_received_stop_notice() &&
1809 this->node_operation.is_part_of_network() &&
1810 this->node_operation.is_user_frontend_open())
1811 {
1812 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1813 }
1814 else
1815 {
1816 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1817 }
1818 return;
1819 };
1820 make_read_only_endpoint(
1821 "/ready/app", HTTP_GET, get_ready_app, no_auth_required)
1822 .set_auto_schema<void, void>()
1823 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1824 .install();
1825
1826 auto get_ready_gov =
1827 [this](const ccf::endpoints::ReadOnlyEndpointContext& ctx) {
1828 auto node_configuration_subsystem =
1829 this->context.get_subsystem<NodeConfigurationSubsystem>();
1830 if (!node_configuration_subsystem)
1831 {
1832 ctx.rpc_ctx->set_error(
1833 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1834 ccf::errors::InternalError,
1835 "NodeConfigurationSubsystem is not available");
1836 return;
1837 }
1838 if (
1839 !node_configuration_subsystem->has_received_stop_notice() &&
1840 this->node_operation.is_accessible_to_members() &&
1841 this->node_operation.is_member_frontend_open())
1842 {
1843 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1844 }
1845 else
1846 {
1847 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1848 }
1849 return;
1850 };
1851 make_read_only_endpoint(
1852 "/ready/gov", HTTP_GET, get_ready_gov, no_auth_required)
1853 .set_auto_schema<void, void>()
1854 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1855 .install();
1856
1857 static constexpr auto snapshot_since_param_key = "since";
1858 // Redirects to endpoint for a single specific snapshot
1859 auto find_snapshot = [this](ccf::endpoints::CommandEndpointContext& ctx) {
1860 auto node_configuration_subsystem =
1861 this->context.get_subsystem<NodeConfigurationSubsystem>();
1862 if (!node_configuration_subsystem)
1863 {
1864 ctx.rpc_ctx->set_error(
1865 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1866 ccf::errors::InternalError,
1867 "NodeConfigurationSubsystem is not available");
1868 return;
1869 }
1870
1871 const auto& snapshots_config =
1872 node_configuration_subsystem->get().node_config.snapshots;
1873
1874 size_t latest_idx = 0;
1875 {
1876 // Get latest_idx from query param, if present
1877 const auto parsed_query =
1878 http::parse_query(ctx.rpc_ctx->get_request_query());
1879
1880 std::string error_reason;
1881 auto snapshot_since = http::get_query_value_opt<ccf::SeqNo>(
1882 parsed_query, snapshot_since_param_key, error_reason);
1883
1884 if (snapshot_since.has_value())
1885 {
1886 if (error_reason != "")
1887 {
1888 ctx.rpc_ctx->set_error(
1889 HTTP_STATUS_BAD_REQUEST,
1890 ccf::errors::InvalidQueryParameterValue,
1891 std::move(error_reason));
1892 return;
1893 }
1894 latest_idx = snapshot_since.value();
1895 }
1896 }
1897
1898 const auto orig_latest = latest_idx;
1899 auto latest_committed_snapshot =
1901 snapshots_config.directory, latest_idx);
1902
1903 if (!latest_committed_snapshot.has_value())
1904 {
1905 ctx.rpc_ctx->set_error(
1906 HTTP_STATUS_NOT_FOUND,
1907 ccf::errors::ResourceNotFound,
1908 fmt::format(
1909 "This node has no committed snapshots since {}", orig_latest));
1910 return;
1911 }
1912
1913 const auto& snapshot_path = latest_committed_snapshot.value();
1914
1915 LOG_DEBUG_FMT("Redirecting to snapshot: {}", snapshot_path);
1916
1917 auto redirect_url = fmt::format("/node/snapshot/{}", snapshot_path);
1918 ctx.rpc_ctx->set_response_header(
1919 ccf::http::headers::LOCATION, redirect_url);
1920 ctx.rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT);
1921 };
1922 make_command_endpoint(
1923 "/snapshot", HTTP_HEAD, find_snapshot, no_auth_required)
1924 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1925 .add_query_parameter<ccf::SeqNo>(
1926 snapshot_since_param_key, ccf::endpoints::OptionalParameter)
1927 .set_openapi_hidden(true)
1928 .install();
1929 make_command_endpoint(
1930 "/snapshot", HTTP_GET, find_snapshot, no_auth_required)
1931 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1932 .add_query_parameter<ccf::SeqNo>(
1933 snapshot_since_param_key, ccf::endpoints::OptionalParameter)
1934 .set_openapi_hidden(true)
1935 .install();
1936
1937 auto get_snapshot = [this](ccf::endpoints::CommandEndpointContext& ctx) {
1938 auto node_configuration_subsystem =
1939 this->context.get_subsystem<NodeConfigurationSubsystem>();
1940 if (!node_configuration_subsystem)
1941 {
1942 ctx.rpc_ctx->set_error(
1943 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1944 ccf::errors::InternalError,
1945 "NodeConfigurationSubsystem is not available");
1946 return;
1947 }
1948
1949 const auto& snapshots_config =
1950 node_configuration_subsystem->get().node_config.snapshots;
1951
1952 std::string snapshot_name;
1953 std::string error;
1954 if (!get_path_param(
1955 ctx.rpc_ctx->get_request_path_params(),
1956 "snapshot_name",
1957 snapshot_name,
1958 error))
1959 {
1960 ctx.rpc_ctx->set_error(
1961 HTTP_STATUS_BAD_REQUEST,
1962 ccf::errors::InvalidResourceName,
1963 std::move(error));
1964 return;
1965 }
1966
1967 files::fs::path snapshot_path =
1968 files::fs::path(snapshots_config.directory) / snapshot_name;
1969
1970 std::ifstream f(snapshot_path, std::ios::binary);
1971 if (!f.good())
1972 {
1973 ctx.rpc_ctx->set_error(
1974 HTTP_STATUS_NOT_FOUND,
1975 ccf::errors::ResourceNotFound,
1976 fmt::format(
1977 "This node does not have a snapshot named {}", snapshot_name));
1978 return;
1979 }
1980
1981 LOG_DEBUG_FMT("Found snapshot: {}", snapshot_path.string());
1982
1983 f.seekg(0, f.end);
1984 const auto total_size = (size_t)f.tellg();
1985
1986 ctx.rpc_ctx->set_response_header("accept-ranges", "bytes");
1987
1988 if (ctx.rpc_ctx->get_request_verb() == HTTP_HEAD)
1989 {
1990 ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1991 ctx.rpc_ctx->set_response_header(
1992 ccf::http::headers::CONTENT_LENGTH, total_size);
1993 return;
1994 }
1995
1996 size_t range_start = 0;
1997 size_t range_end = total_size;
1998 {
1999 const auto range_header = ctx.rpc_ctx->get_request_header("range");
2000 if (range_header.has_value())
2001 {
2002 LOG_TRACE_FMT("Parsing range header {}", range_header.value());
2003
2004 auto [unit, ranges] =
2005 ccf::nonstd::split_1(range_header.value(), "=");
2006 if (unit != "bytes")
2007 {
2008 ctx.rpc_ctx->set_error(
2009 HTTP_STATUS_BAD_REQUEST,
2010 ccf::errors::InvalidHeaderValue,
2011 "Only 'bytes' is supported as a Range header unit");
2012 return;
2013 }
2014
2015 if (ranges.find(",") != std::string::npos)
2016 {
2017 ctx.rpc_ctx->set_error(
2018 HTTP_STATUS_BAD_REQUEST,
2019 ccf::errors::InvalidHeaderValue,
2020 "Multiple ranges are not supported");
2021 return;
2022 }
2023
2024 const auto segments = ccf::nonstd::split(ranges, "-");
2025 if (segments.size() != 2)
2026 {
2027 ctx.rpc_ctx->set_error(
2028 HTTP_STATUS_BAD_REQUEST,
2029 ccf::errors::InvalidHeaderValue,
2030 fmt::format(
2031 "Invalid format, cannot parse range in {}",
2032 range_header.value()));
2033 return;
2034 }
2035
2036 const auto s_range_start = segments[0];
2037 const auto s_range_end = segments[1];
2038
2039 if (!s_range_start.empty())
2040 {
2041 {
2042 const auto [p, ec] = std::from_chars(
2043 s_range_start.begin(), s_range_start.end(), range_start);
2044 if (ec != std::errc())
2045 {
2046 ctx.rpc_ctx->set_error(
2047 HTTP_STATUS_BAD_REQUEST,
2048 ccf::errors::InvalidHeaderValue,
2049 fmt::format(
2050 "Unable to parse start of range value {} in {}",
2051 s_range_start,
2052 range_header.value()));
2053 return;
2054 }
2055 }
2056
2057 if (range_start > total_size)
2058 {
2059 ctx.rpc_ctx->set_error(
2060 HTTP_STATUS_BAD_REQUEST,
2061 ccf::errors::InvalidHeaderValue,
2062 fmt::format(
2063 "Start of range {} is larger than total file size {}",
2064 range_start,
2065 total_size));
2066 return;
2067 }
2068
2069 if (!s_range_end.empty())
2070 {
2071 // Fully-specified range, like "X-Y"
2072 {
2073 const auto [p, ec] = std::from_chars(
2074 s_range_end.begin(), s_range_end.end(), range_end);
2075 if (ec != std::errc())
2076 {
2077 ctx.rpc_ctx->set_error(
2078 HTTP_STATUS_BAD_REQUEST,
2079 ccf::errors::InvalidHeaderValue,
2080 fmt::format(
2081 "Unable to parse end of range value {} in {}",
2082 s_range_end,
2083 range_header.value()));
2084 return;
2085 }
2086 }
2087
2088 if (range_end > total_size)
2089 {
2090 ctx.rpc_ctx->set_error(
2091 HTTP_STATUS_BAD_REQUEST,
2092 ccf::errors::InvalidHeaderValue,
2093 fmt::format(
2094 "End of range {} is larger than total file size {}",
2095 range_end,
2096 total_size));
2097 return;
2098 }
2099
2100 if (range_end < range_start)
2101 {
2102 ctx.rpc_ctx->set_error(
2103 HTTP_STATUS_BAD_REQUEST,
2104 ccf::errors::InvalidHeaderValue,
2105 fmt::format(
2106 "Invalid range: Start ({}) and end ({}) out of order",
2107 range_start,
2108 range_end));
2109 return;
2110 }
2111 }
2112 else
2113 {
2114 // Else this is an open-ended range like "X-"
2115 range_end = total_size;
2116 }
2117 }
2118 else
2119 {
2120 if (!s_range_end.empty())
2121 {
2122 // Negative range, like "-Y"
2123 size_t offset;
2124 const auto [p, ec] = std::from_chars(
2125 s_range_end.begin(), s_range_end.end(), offset);
2126 if (ec != std::errc())
2127 {
2128 ctx.rpc_ctx->set_error(
2129 HTTP_STATUS_BAD_REQUEST,
2130 ccf::errors::InvalidHeaderValue,
2131 fmt::format(
2132 "Unable to parse end of range offset value {} in {}",
2133 s_range_end,
2134 range_header.value()));
2135 return;
2136 }
2137
2138 range_end = total_size;
2139 range_start = range_end - offset;
2140 }
2141 else
2142 {
2143 ctx.rpc_ctx->set_error(
2144 HTTP_STATUS_BAD_REQUEST,
2145 ccf::errors::InvalidHeaderValue,
2146 "Invalid range: Must contain range-start or range-end");
2147 return;
2148 }
2149 }
2150 }
2151 }
2152
2153 const auto range_size = range_end - range_start;
2154
2156 "Reading {}-byte range from {} to {}",
2157 range_size,
2158 range_start,
2159 range_end);
2160
2161 // Read requested range into buffer
2162 std::vector<uint8_t> contents(range_size);
2163 f.seekg(range_start);
2164 f.read((char*)contents.data(), contents.size());
2165 f.close();
2166
2167 // Build successful response
2168 ctx.rpc_ctx->set_response_status(HTTP_STATUS_PARTIAL_CONTENT);
2169 ctx.rpc_ctx->set_response_header(
2170 ccf::http::headers::CONTENT_TYPE,
2171 ccf::http::headervalues::contenttype::OCTET_STREAM);
2172 ctx.rpc_ctx->set_response_body(std::move(contents));
2173
2174 // Partial Content responses describe the current response in
2175 // Content-Range
2176 ctx.rpc_ctx->set_response_header(
2177 "content-range",
2178 fmt::format("bytes {}-{}/{}", range_start, range_end, total_size));
2179 };
2180 make_command_endpoint(
2181 "/snapshot/{snapshot_name}", HTTP_HEAD, get_snapshot, no_auth_required)
2182 .set_forwarding_required(endpoints::ForwardingRequired::Never)
2183 .set_openapi_hidden(true)
2184 .install();
2185 make_command_endpoint(
2186 "/snapshot/{snapshot_name}", HTTP_GET, get_snapshot, no_auth_required)
2187 .set_forwarding_required(endpoints::ForwardingRequired::Never)
2188 .set_openapi_hidden(true)
2189 .install();
2190 }
2191 };
2192
2194 {
2195 protected:
2197
2198 public:
2200 RpcFrontend(*network.tables, node_endpoints, context),
2201 node_endpoints(network, context)
2202 {}
2203 };
2204}
Definition node_operation_interface.h:23
virtual bool is_accessible_to_members() const =0
virtual bool is_user_frontend_open()=0
virtual ccf::kv::Version get_startup_snapshot_seqno()=0
virtual QuoteVerificationResult verify_quote(ccf::kv::ReadOnlyTx &tx, const QuoteInfo &quote_info, const std::vector< uint8_t > &expected_node_public_key_der, pal::PlatformAttestationMeasurement &measurement)=0
virtual bool is_in_initialised_state() const =0
virtual ccf::crypto::Pem get_self_signed_node_certificate()=0
virtual bool is_reading_private_ledger() const =0
virtual ExtendedState state()=0
virtual SessionMetrics get_session_metrics()=0
virtual bool is_member_frontend_open()=0
virtual bool can_replicate()=0
virtual const ccf::COSESignaturesConfig & get_cose_signatures_config()=0
virtual bool is_reading_public_ledger() const =0
virtual bool is_part_of_network() const =0
virtual bool is_part_of_public_network() const =0
virtual ccf::kv::Version get_last_recovered_signed_idx()=0
static std::optional< pal::snp::Attestation > get_snp_attestation(const QuoteInfo &quote_info)
Definition quote.cpp:145
static std::optional< HostData > get_host_data(const QuoteInfo &quote_info)
Definition quote.cpp:168
static std::optional< pal::PlatformAttestationMeasurement > get_measurement(const QuoteInfo &quote_info)
Definition quote.cpp:128
Definition common_endpoint_registry.h:16
void init_handlers() override
Definition common_endpoint_registry.cpp:69
static bool is_service_created(ccf::kv::ReadOnlyTx &tx, const ccf::crypto::Pem &expected_service_cert)
Definition internal_tables_access.h:507
static void init_configuration(ccf::kv::Tx &tx, const ServiceConfiguration &configuration)
Definition internal_tables_access.h:888
static void trust_node_snp_host_data(ccf::kv::Tx &tx, const HostData &host_data, const std::optional< HostDataMetadata > &security_policy=std::nullopt)
Definition internal_tables_access.h:785
static MemberId add_member(ccf::kv::Tx &tx, const NewMember &member_pub_info)
Definition internal_tables_access.h:183
static void set_constitution(ccf::kv::Tx &tx, const std::string &constitution)
Definition internal_tables_access.h:731
static void trust_node_uvm_endorsements(ccf::kv::Tx &tx, const std::optional< pal::UVMEndorsements > &uvm_endorsements)
Definition internal_tables_access.h:805
static void create_service(ccf::kv::Tx &tx, const ccf::crypto::Pem &service_cert, ccf::TxID create_txid, nlohmann::json service_data=nullptr, bool recovering=false)
Definition internal_tables_access.h:464
static void trust_node_measurement(ccf::kv::Tx &tx, const pal::PlatformAttestationMeasurement &node_measurement, const QuoteFormat &platform)
Definition internal_tables_access.h:737
static void retire_active_nodes(ccf::kv::Tx &tx)
Definition internal_tables_access.h:56
static void add_node(ccf::kv::Tx &tx, const NodeId &id, const NodeInfo &node_info)
Definition internal_tables_access.h:428
static void trust_node_virtual_host_data(ccf::kv::Tx &tx, const HostData &host_data)
Definition internal_tables_access.h:777
static void trust_node_snp_tcb_version(ccf::kv::Tx &tx, pal::snp::Attestation &attestation)
Definition internal_tables_access.h:850
Definition json.h:26
Definition node_configuration_subsystem.h:13
virtual const NodeConfigurationState & get() override
Definition node_configuration_subsystem.h:31
Definition node_frontend.h:168
void init_handlers() override
Definition node_frontend.h:430
bool apply_uncommitted_tx_backpressure() const override
Definition node_frontend.h:172
NodeEndpoints(NetworkState &network_, ccf::AbstractNodeContext &context_)
Definition node_frontend.h:418
Definition node_frontend.h:2194
NodeEndpoints node_endpoints
Definition node_frontend.h:2196
NodeRpcFrontend(NetworkState &network, ccf::AbstractNodeContext &context)
Definition node_frontend.h:2199
Definition frontend.h:34
Definition pem.h:18
Definition committable_tx.h:18
M::ReadOnlyHandle * ro(M &m)
Definition tx.h:169
Definition tx.h:201
M::Handle * rw(M &m)
Definition tx.h:212
#define DECLARE_JSON_TYPE_WITH_BASE(TYPE, BASE)
Definition json.h:665
#define DECLARE_JSON_REQUIRED_FIELDS(TYPE,...)
Definition json.h:714
#define DECLARE_JSON_TYPE(TYPE)
Definition json.h:663
#define DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(TYPE)
Definition json.h:690
#define DECLARE_JSON_OPTIONAL_FIELDS(TYPE,...)
Definition json.h:786
#define LOG_INFO_FMT
Definition logger.h:362
#define LOG_TRACE_FMT
Definition logger.h:356
#define LOG_DEBUG_FMT
Definition logger.h:357
#define LOG_FAIL_FMT
Definition logger.h:363
std::string error_string(unsigned long ec)
Returns the error string from an error code.
Definition openssl_wrappers.h:35
ccf::crypto::Pem public_key_pem_from_cert(const std::vector< uint8_t > &der)
Definition verifier.cpp:48
VerifierPtr make_verifier(const std::vector< uint8_t > &cert)
Definition verifier.cpp:18
Pem public_key_pem_from_csr(const Pem &signing_request)
Definition csr.h:16
std::vector< uint8_t > public_key_der_from_cert(const std::vector< uint8_t > &der)
Definition verifier.cpp:43
@ OptionalParameter
Definition endpoint.h:122
Definition app_interface.h:14
EntityId< NodeIdFormatter > NodeId
Definition entity_id.h:155
ServiceStatus
Definition service.h:13
constexpr auto get_actor_prefix(ActorsType at)
Definition actors.h:31
constexpr char const * api_result_to_str(ApiResult result)
Definition base_endpoint_registry.h:35
@ error
Definition tls_session.h:24
QuoteVerificationResult
Definition quote.h:18
std::optional< ccf::ClaimsDigest::Digest > get_create_tx_claims_digest(ccf::kv::ReadOnlyTx &tx)
Definition create_tx_claims_digest.cpp:10
NodeId compute_node_id_from_pubk_der(const std::vector< uint8_t > &node_pubk_der)
Definition nodes.h:30
jsonhandler::JsonAdapterResponse make_success()
Definition json_handler.cpp:99
endpoints::CommandEndpointFunction json_command_adapter(const CommandHandlerWithJson &f)
Definition json_handler.cpp:145
ReconfigurationType
Definition reconfiguration_type.h:10
@ ONE_TRANSACTION
Definition reconfiguration_type.h:11
std::map< std::string, ConsensusNodeConfig > ConsensusConfig
Definition node_frontend.h:136
QuoteFormat
Definition quote_info.h:12
NodeStatus
Definition node_info.h:18
endpoints::ReadOnlyEndpointFunction json_read_only_adapter(const ReadOnlyHandlerWithJson &f)
Definition json_handler.cpp:136
endpoints::EndpointFunction json_adapter(const HandlerJsonParamsAndForward &f)
Definition json_handler.cpp:128
jsonhandler::JsonAdapterResponse make_error(ccf::http_status status, const std::string &code, const std::string &msg)
Definition json_handler.cpp:115
ActorsType
Definition actors.h:11
uint64_t SeqNo
Definition tx_id.h:36
Definition consensus_types.h:23
Definition configuration.h:14
std::optional< fs::path > find_latest_committed_snapshot_in_directory(const fs::path &directory, size_t &latest_committed_snapshot_idx)
Definition filenames.h:135
Definition node_context.h:12
Definition node_frontend.h:51
Snapshots snapshots
Definition startup_config.h:102
Definition node_frontend.h:139
ccf::kv::ConsensusDetails details
Definition node_frontend.h:140
Definition node_frontend.h:129
std::string address
Definition node_frontend.h:130
Definition node_call_types.h:58
Definition node_frontend.h:73
std::vector< Attestation > attestations
Definition node_frontend.h:74
Definition node_frontend.h:69
void In
Definition node_frontend.h:70
Definition call_types.h:71
nlohmann::json service_data
Definition call_types.h:77
std::optional< NodeId > primary_id
Definition call_types.h:75
std::optional< ccf::TxID > current_service_create_txid
Definition call_types.h:78
size_t recovery_count
Definition call_types.h:76
ServiceStatus service_status
Definition call_types.h:72
std::optional< ccf::View > current_view
Definition call_types.h:74
ccf::crypto::Pem service_certificate
Definition call_types.h:73
Definition call_types.h:85
NodeStatus status
Definition call_types.h:87
ccf::NodeInfoNetwork::RpcInterfaces rpc_interfaces
Definition call_types.h:89
Definition call_types.h:100
std::vector< GetNode::NodeInfo > nodes
Definition call_types.h:101
Definition node_frontend.h:60
std::vector< Quote > quotes
Definition node_frontend.h:61
Definition node_frontend.h:56
void In
Definition node_frontend.h:57
Definition node_frontend.h:158
ccf::crypto::Pem previous_service_identity
Definition node_frontend.h:159
Definition node_frontend.h:156
Definition node_call_types.h:29
std::optional< ccf::kv::Version > last_recovered_seqno
Definition node_call_types.h:37
std::optional< ccf::kv::Version > recovery_target_seqno
Definition node_call_types.h:36
bool stop_notice
Definition node_call_types.h:39
ccf::NodeId node_id
Definition node_call_types.h:30
ccf::kv::Version startup_seqno
Definition node_call_types.h:33
ccf::NodeStartupState state
Definition node_call_types.h:31
ccf::kv::Version last_signed_seqno
Definition node_call_types.h:32
Definition node_call_types.h:25
Definition node_call_types.h:48
bool unsafe
Definition node_call_types.h:51
std::string quickjs_version
Definition node_call_types.h:50
std::string ccf_version
Definition node_call_types.h:49
Definition node_call_types.h:44
Definition jsengine.h:12
Definition node_frontend.h:110
size_t failures
Definition node_frontend.h:113
size_t attempts
Definition node_frontend.h:111
size_t successes
Definition node_frontend.h:112
Definition node_frontend.h:90
bool bytecode_used
Definition node_frontend.h:92
uint64_t max_execution_time
Definition node_frontend.h:95
uint64_t max_heap_size
Definition node_frontend.h:93
uint64_t max_stack_size
Definition node_frontend.h:94
uint64_t max_cached_interpreters
Definition node_frontend.h:96
uint64_t bytecode_size
Definition node_frontend.h:91
Definition node_call_types.h:83
Definition node_call_types.h:101
Definition node_call_types.h:94
std::optional< NodeId > node_id
Definition node_call_types.h:98
std::optional< NetworkInfo > network_info
Definition node_call_types.h:150
NodeStatus node_status
Definition node_call_types.h:95
Definition jwt.h:92
Definition node_call_types.h:159
Definition node_call_types.h:155
Definition network_state.h:12
std::shared_ptr< LedgerSecrets > ledger_secrets
Definition network_state.h:14
std::unique_ptr< NetworkIdentity > identity
Definition network_state.h:13
const JwtIssuers jwt_issuers
Definition network_tables.h:167
const NodeEndorsedCertificates node_endorsed_certificates
Definition network_tables.h:88
const Service service
Definition network_tables.h:193
const Configuration config
Definition network_tables.h:201
const JSEngine js_engine
Definition network_tables.h:149
const ModulesQuickJsBytecode modules_quickjs_bytecode
Definition network_tables.h:144
const ModulesQuickJsVersion modules_quickjs_version
Definition network_tables.h:146
const Nodes nodes
Definition network_tables.h:87
const ccf::StartupConfig & node_config
Definition node_configuration_interface.h:16
RpcInterfaces rpc_interfaces
RPC interfaces.
Definition node_info_network.h:150
Definition node_info.h:30
QuoteInfo quote_info
Node enclave quote.
Definition node_info.h:32
NodeStatus status
Node status.
Definition node_info.h:36
nlohmann::json node_data
Definition node_info.h:57
Definition node_frontend.h:82
ccf::SessionMetrics sessions
Definition node_frontend.h:83
Describes a quote (attestation) from trusted hardware.
Definition quote_info.h:26
QuoteFormat format
Quote format.
Definition quote_info.h:28
std::optional< std::vector< uint8_t > > uvm_endorsements
UVM endorsements (SNP-only)
Definition quote_info.h:34
std::vector< uint8_t > quote
Enclave quote.
Definition quote_info.h:30
std::vector< uint8_t > endorsements
Quote endorsements.
Definition quote_info.h:32
Definition node_frontend.h:34
std::vector< uint8_t > raw
Definition node_frontend.h:36
std::optional< std::vector< uint8_t > > uvm_endorsements
Definition node_frontend.h:42
std::string measurement
Definition node_frontend.h:40
NodeId node_id
Definition node_frontend.h:35
QuoteFormat format
Definition node_frontend.h:38
std::vector< uint8_t > endorsements
Definition node_frontend.h:37
Definition node_frontend.h:147
ccf::crypto::Pem self_signed_certificate
Definition node_frontend.h:148
Definition service_config.h:14
Definition session_metrics.h:13
Definition node_frontend.h:120
JsonWebKeySet jwks
Definition node_frontend.h:122
std::string issuer
Definition node_frontend.h:121
Definition endpoint_context.h:24
Definition endpoint_context.h:70
Definition endpoint_registry.h:42
std::string dispatch_path
Definition endpoint_registry.h:48
std::string method
Definition endpoint_registry.h:43
Definition kv_types.h:184
Definition mem.h:16