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
9#include "ccf/http_query.h"
10#include "ccf/js/core/context.h"
11#include "ccf/json_handler.h"
12#include "ccf/node/quote.h"
13#include "ccf/odata_error.h"
14#include "ccf/pal/attestation.h"
16#include "ccf/version.h"
17#include "crypto/certs.h"
18#include "crypto/csr.h"
19#include "ds/files.h"
20#include "ds/std_formatters.h"
21#include "frontend.h"
22#include "node/cose_common.h"
23#include "node/network_state.h"
26#include "node/rpc/no_create_tx_claims_digest.cpp" // NOLINT(bugprone-suspicious-include)
31#include "node_interface.h"
36#include "snapshots/filenames.h"
37
38#include <llhttp/llhttp.h>
39#include <stdexcept>
40
41namespace ccf
42{
43 struct Quote
44 {
46 std::vector<uint8_t> raw;
47 std::vector<uint8_t> endorsements;
49
50 std::string measurement; // < Hex-encoded
51
52 std::optional<std::vector<uint8_t>> uvm_endorsements =
53 std::nullopt; // SNP only
54 };
55
57 DECLARE_JSON_REQUIRED_FIELDS(Quote, node_id, raw, endorsements, format);
58 DECLARE_JSON_OPTIONAL_FIELDS(Quote, measurement, uvm_endorsements);
59
60 struct Attestation : public Quote
61 {};
64
65 struct GetQuotes
66 {
67 using In = void;
68
69 struct Out
70 {
71 std::vector<Quote> quotes;
72 };
73 };
74
77
79 {
80 using In = void;
81
82 struct Out
83 {
84 std::vector<Attestation> attestations;
85 };
86 };
87
90
95
98
100 {
101 using In = void;
102
103 struct Out
104 {
106 };
107 };
108
111
113 {
114 uint64_t bytecode_size = 0;
115 bool bytecode_used = false;
116 uint64_t max_heap_size = 0;
117 uint64_t max_stack_size = 0;
118 uint64_t max_execution_time = 0;
120 };
121
125 bytecode_size,
126 bytecode_used,
127 max_heap_size,
128 max_stack_size,
129 max_execution_time,
130 max_cached_interpreters);
131
133 {
134 size_t attempts = 0;
135 size_t successes = 0;
136 size_t failures = 0;
137 };
138
141 JWTRefreshMetrics, attempts, successes, failures);
142
144 {
145 std::string issuer;
147 };
148
151
153 {
154 std::string address;
155 };
156
159
160 using ConsensusConfig = std::map<std::string, ConsensusNodeConfig>;
161
166
169
174
177 SelfSignedNodeCertificateInfo, self_signed_certificate);
178
186
189 GetServicePreviousIdentity::Out, previous_service_identity);
190
192 {
193 public:
194 // The node frontend is exempt from backpressure rules to enable an operator
195 // to access a node that is not making progress.
196 [[nodiscard]] bool apply_uncommitted_tx_backpressure() const override
197 {
198 return false;
199 }
200
201 private:
202 NetworkState& network;
203 ccf::AbstractNodeOperation& node_operation;
204
205 struct ExistingNodeInfo
206 {
207 NodeId node_id;
208 std::optional<ccf::kv::Version> ledger_secret_seqno = std::nullopt;
209 std::optional<ccf::crypto::Pem> endorsed_certificate = std::nullopt;
210 };
211
212 std::optional<ExistingNodeInfo> check_node_exists(
213 ccf::kv::Tx& tx,
214 const std::vector<uint8_t>& self_signed_node_der,
215 std::optional<NodeStatus> node_status = std::nullopt)
216 {
217 // Check that a node exists by looking up its public key in the nodes
218 // table.
219 auto* nodes = tx.ro(network.nodes);
220 auto* endorsed_node_certificates =
221 tx.ro(network.node_endorsed_certificates);
222
224 "Check node exists with certificate [{}]", self_signed_node_der);
225 auto pk_pem = ccf::crypto::public_key_pem_from_cert(self_signed_node_der);
226
227 std::optional<ExistingNodeInfo> existing_node_info = std::nullopt;
228 nodes->foreach([&existing_node_info,
229 &pk_pem,
230 &node_status,
231 &endorsed_node_certificates](
232 const NodeId& nid, const NodeInfo& ni) {
233 if (
234 ni.public_key == pk_pem &&
235 (!node_status.has_value() || ni.status == node_status.value()))
236 {
237 existing_node_info = {
238 nid, ni.ledger_secret_seqno, endorsed_node_certificates->get(nid)};
239 return false;
240 }
241 return true;
242 });
243
244 return existing_node_info;
245 }
246
247 std::optional<NodeId> check_conflicting_node_network(
248 ccf::kv::Tx& tx, const NodeInfoNetwork& node_info_network)
249 {
250 auto* nodes = tx.rw(network.nodes);
251
252 std::optional<NodeId> duplicate_node_id = std::nullopt;
253 nodes->foreach([&node_info_network, &duplicate_node_id](
254 const NodeId& nid, const NodeInfo& ni) {
255 if (
256 node_info_network.node_to_node_interface.published_address ==
257 ni.node_to_node_interface.published_address &&
258 ni.status != NodeStatus::RETIRED)
259 {
260 duplicate_node_id = nid;
261 return false;
262 }
263 return true;
264 });
265
266 return duplicate_node_id;
267 }
268
269 auto add_node(
270 ccf::kv::Tx& tx,
271 const std::vector<uint8_t>& node_der,
272 const JoinNetworkNodeToNode::In& in,
273 NodeStatus node_status,
274 ServiceStatus service_status)
275 {
276 auto* nodes = tx.rw(network.nodes);
277 auto* node_endorsed_certificates =
278 tx.rw(network.node_endorsed_certificates);
279
280 auto conflicting_node_id =
281 check_conflicting_node_network(tx, in.node_info_network);
282 if (conflicting_node_id.has_value())
283 {
284 return make_error(
285 HTTP_STATUS_BAD_REQUEST,
286 ccf::errors::NodeAlreadyExists,
287 fmt::format(
288 "A node with the same published node address {} already exists "
289 "(node id: {}).",
290 in.node_info_network.node_to_node_interface.published_address,
291 conflicting_node_id.value()));
292 }
293
294 auto pubk_der = ccf::crypto::public_key_der_from_cert(node_der);
295 NodeId joining_node_id = compute_node_id_from_pubk_der(pubk_der);
296
297 pal::PlatformAttestationMeasurement measurement;
298
299 auto network_identity_subsystem =
300 context
301 .template get_subsystem<ccf::NetworkIdentitySubsystemInterface>();
302
303 QuoteVerificationResult verify_result = this->node_operation.verify_quote(
304 tx,
305 in.quote_info,
306 pubk_der,
307 measurement,
308 in.code_transparent_statement,
309 network_identity_subsystem);
310 if (verify_result != QuoteVerificationResult::Verified)
311 {
312 const auto [code, message] = quote_verification_error(verify_result);
313 return make_error(code, ccf::errors::InvalidQuote, message);
314 }
315
316 // Check if the joining node's signing mode is acceptable
317 if (
318 in.ledger_sign_mode.has_value() &&
319 in.ledger_sign_mode.value() == ccf::LedgerSignMode::Dual &&
321 {
322 return make_error(
323 HTTP_STATUS_BAD_REQUEST,
324 ccf::errors::InvalidInput,
325 "This network does not accept nodes running in Dual signing "
326 "mode. The joining node must use COSE-only signing.");
327 }
328
329 std::optional<ccf::kv::Version> ledger_secret_seqno = std::nullopt;
330 if (node_status == NodeStatus::TRUSTED)
331 {
332 ledger_secret_seqno =
333 this->network.ledger_secrets->get_latest(tx).first;
334 }
335
336 // Note: All new nodes should specify a CSR from 2.x
337 auto client_public_key_pem =
339 if (in.certificate_signing_request.has_value())
340 {
341 // Verify that client's public key matches the one specified in the CSR
342 auto csr_public_key_pem = ccf::crypto::public_key_pem_from_csr(
343 in.certificate_signing_request.value());
344 if (client_public_key_pem != csr_public_key_pem)
345 {
346 return make_error(
347 HTTP_STATUS_BAD_REQUEST,
348 ccf::errors::CSRPublicKeyInvalid,
349 "Public key in CSR does not match TLS client identity.");
350 }
351 }
352
353 NodeInfo node_info = {
354 in.node_info_network,
355 in.quote_info,
356 in.public_encryption_key,
357 node_status,
358 ledger_secret_seqno,
359 measurement.hex_str(),
360 in.certificate_signing_request,
361 client_public_key_pem,
362 in.node_data};
363
364 nodes->put(joining_node_id, node_info);
365
366 if (in.sealing_recovery_data.has_value())
367 {
368 const auto& [sealing_keys, sealing_recovery_name] =
369 in.sealing_recovery_data.value();
370 auto* sealed_recovery_keys =
371 tx.rw<SealedRecoveryKeys>(Tables::SEALED_RECOVERY_KEYS);
372 sealed_recovery_keys->put(joining_node_id, sealing_keys);
373 auto* local_sealing_node_id_map =
374 tx.rw<LocalSealingNodeIdMap>(Tables::SEALING_RECOVERY_NAMES);
375 local_sealing_node_id_map->put(sealing_recovery_name, joining_node_id);
376 }
377
378 LOG_INFO_FMT("Node {} added as {}", joining_node_id, node_status);
379
380 JoinNetworkNodeToNode::Out rep;
381 rep.node_status = node_status;
382 rep.node_id = joining_node_id;
383
384 if (node_status == NodeStatus::TRUSTED)
385 {
386 node_operation.shuffle_sealed_shares(tx);
387 // Joining node only submit a CSR from 2.x
388 std::optional<ccf::crypto::Pem> endorsed_certificate = std::nullopt;
389 if (in.certificate_signing_request.has_value())
390 {
391 // For a pre-open service, extract the validity period of self-signed
392 // node certificate and use it verbatim in endorsed certificate
393 auto [valid_from, valid_to] =
394 ccf::crypto::make_verifier(node_der)->validity_period();
395 endorsed_certificate = ccf::crypto::create_endorsed_cert(
396 in.certificate_signing_request.value(),
397 valid_from,
398 valid_to,
399 this->network.identity->priv_key,
400 this->network.identity->cert);
401
402 node_endorsed_certificates->put(
403 joining_node_id, {endorsed_certificate.value()});
404 }
405
406 rep.network_info = JoinNetworkNodeToNode::Out::NetworkInfo{
407 node_operation.is_part_of_public_network(),
408 node_operation.get_last_recovered_signed_idx(),
409 this->network.ledger_secrets->get(tx),
410 *this->network.identity,
411 service_status,
412 endorsed_certificate,
413 node_operation.get_cose_signatures_config()};
414 }
415 return make_success(rep);
416 }
417
418 JWTRefreshMetrics jwt_refresh_metrics;
419 void handle_event_request_completed(
420 const ccf::endpoints::RequestCompletedEvent& event) override
421 {
422 if (event.method == "POST" && event.dispatch_path == "/jwt_keys/refresh")
423 {
424 jwt_refresh_metrics.attempts += 1;
425 int status_category = event.status / 100;
426 if (status_category >= 4)
427 {
428 jwt_refresh_metrics.failures += 1;
429 }
430 else if (status_category == 2)
431 {
432 jwt_refresh_metrics.successes += 1;
433 }
434 }
435 }
436
437 public:
440 network(network_),
441 node_operation(*context_.get_subsystem<ccf::AbstractNodeOperation>())
442 {
443 openapi_info.title = "CCF Public Node API";
444 openapi_info.description =
445 "This API provides public, uncredentialed access to service and node "
446 "state.";
447 openapi_info.document_version = "5.0.5";
448 }
449
450 void init_handlers() override
451 {
453
454 auto accept = [this](auto& args, const nlohmann::json& params) {
455 const auto in = params.get<JoinNetworkNodeToNode::In>();
456
457 // Not part of network => Internal error
458 if (
459 !this->node_operation.is_part_of_network() &&
460 !this->node_operation.is_part_of_public_network() &&
461 !this->node_operation.is_reading_private_ledger())
462 {
463 const std::string payload =
464 "Target node should be part of network to accept new nodes.";
465 LOG_INFO_FMT("Join request rejected: {}", payload);
466 return make_error(
467 HTTP_STATUS_INTERNAL_SERVER_ERROR,
468 ccf::errors::InternalError,
469 payload);
470 }
471
472 // No service => Internal error
473 auto service = args.tx.rw(this->network.service);
474 auto active_service = service->get();
475 if (!active_service.has_value())
476 {
477 const std::string payload =
478 "No service is available to accept new node.";
479 LOG_INFO_FMT("Join request rejected: {}", payload);
480 return make_error(
481 HTTP_STATUS_INTERNAL_SERVER_ERROR,
482 ccf::errors::InternalError,
483 payload);
484 }
485
486 auto nodes = args.tx.ro(network.nodes);
487
488 // If already joined => return equivalent response
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())
492 {
494
495 // If the node already exists, return network secrets if is already
496 // trusted. Otherwise, only return its status
497 auto node_info = nodes->get(existing_node_info->node_id);
498 auto node_status = node_info->status;
499 rep.node_status = node_status;
500 rep.node_id = existing_node_info->node_id;
501 if (node_status == NodeStatus::TRUSTED)
502 {
504 node_operation.is_part_of_public_network(),
505 node_operation.get_last_recovered_signed_idx(),
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,
511 node_operation.get_cose_signatures_config());
512
514 "Join request accepted: {} already marked as TRUSTED",
515 existing_node_info->node_id);
516 return make_success(rep);
517 }
518
519 if (node_status == NodeStatus::PENDING)
520 {
521 // Only return node status and ID
523 "Join request accepted: {} already marked as PENDING",
524 existing_node_info->node_id);
525 return make_success(rep);
526 }
527
528 const std::string payload = fmt::format(
529 "Joining node is not in expected state ({}).", node_status);
530 LOG_INFO_FMT("Join request rejected: {}", payload);
531 return make_error(
532 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidNodeState, payload);
533 }
534
535 // Not the primary => Redirect if possible to primary
536 if (consensus != nullptr && !this->node_operation.can_replicate())
537 {
538 auto primary_id = consensus->primary();
539 if (primary_id.has_value())
540 {
541 const auto address = node::get_redirect_address_for_node(
542 args, args.tx, primary_id.value());
543 if (!address.has_value())
544 {
546 "Join request rejected: no redirect address for "
547 "primary {}",
548 primary_id.value());
550 }
551
552 args.rpc_ctx->set_response_header(
553 http::headers::LOCATION,
554 fmt::format("https://{}/node/join", address.value()));
555
556 const std::string payload =
557 "Node is not primary; cannot handle write";
559 "Join request redirected to primary {} at {}: {}",
560 primary_id.value(),
561 address.value(),
562 payload);
563 return make_error(
564 HTTP_STATUS_PERMANENT_REDIRECT,
565 ccf::errors::NodeCannotHandleRequest,
566 "payload");
567 }
568
569 const std::string payload = "Primary unknown";
570 LOG_INFO_FMT("Join request rejected: {}", payload);
571 return make_error(
572 HTTP_STATUS_INTERNAL_SERVER_ERROR,
573 ccf::errors::InternalError,
574 payload);
575 }
576
577 // Joiner's snapshot too old => StartupSeqnoIsOld
578 // (causes joiner to fetch a more recent snapshot)
579 //
580 // The joiner always wants to use the most recent snapshot.
581 // However this will result in the joiner chasing the primary if
582 // snapshot production period ~= snapshot fetching delay
583 //
584 // So we have hysteresis in the fetching constraint:
585 // If the joiner has already fetched a snapshot: joiner seqno > startup
586 // snapshot seqno Otherwise: joiner seqno > latest snapshot on disk
587 // seqno
588 auto this_startup_seqno =
589 this->node_operation.get_startup_snapshot_seqno();
590 ccf::kv::Version required_seqno = this_startup_seqno;
591 // If the joiner does not enable fetching, or is a legacy node,
592 // join_fetch_count is unset and we should use the required bound to
593 // prevent it chasing the primary.
594 // Otherwise if this is the first request, use the preferred bound
595 bool using_preferred_bound =
596 (in.join_fetch_count.has_value() && in.join_fetch_count.value() == 0);
597 if (using_preferred_bound)
598 {
599 auto node_configuration_subsystem =
600 this->context.get_subsystem<NodeConfigurationSubsystem>();
601 if (node_configuration_subsystem != nullptr)
602 {
603 const auto& snapshots_config =
604 node_configuration_subsystem->get().node_config.snapshots;
605 const auto latest_committed_snapshot =
607 snapshots_config.directory);
608 if (latest_committed_snapshot.has_value())
609 {
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(
614 required_seqno,
615 static_cast<ccf::kv::Version>(latest_snapshot_seqno));
616 }
617 }
618 }
619 if (
620 in.startup_seqno.has_value() &&
621 in.startup_seqno.value() < required_seqno)
622 {
623 // Make sure that the joiner's snapshot is more recent than this
624 // node's snapshot. Otherwise, the joiner may not be given all the
625 // ledger secrets required to replay historical transactions.
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 "
629 "be used instead.",
630 in.startup_seqno.value(),
631 using_preferred_bound ? "latest_on_disk_seqno" : "startup_seqno",
632 required_seqno,
633 required_seqno);
634 LOG_INFO_FMT("Join request rejected: {}", payload);
635 return make_error(
636 HTTP_STATUS_BAD_REQUEST, ccf::errors::StartupSeqnoIsOld, payload);
637 }
638
639 auto joining_node_status = NodeStatus::PENDING;
640 // If the service is opening, new nodes are trusted straight away
641 if (
642 active_service->status == ServiceStatus::OPENING ||
643 active_service->status == ServiceStatus::RECOVERING)
644 {
645 joining_node_status = NodeStatus::TRUSTED;
646 }
647
648 return add_node(
649 args.tx,
650 args.rpc_ctx->get_session_context()->caller_cert,
651 in,
652 joining_node_status,
653 active_service->status);
654 };
655 make_endpoint("/join", HTTP_POST, json_adapter(accept), no_auth_required)
656 .set_forwarding_required(endpoints::ForwardingRequired::Never)
657 .set_openapi_hidden(true)
658 .install();
659
660 auto set_retired_committed = [this](auto& ctx, nlohmann::json&&) {
661 auto nodes = ctx.tx.rw(network.nodes);
662 nodes->foreach([&nodes](const auto& node_id, auto node_info) {
663 auto gc_node = nodes->get_globally_committed(node_id);
664 if (
665 gc_node.has_value() &&
666 gc_node->status == ccf::NodeStatus::RETIRED &&
667 !node_info.retired_committed)
668 {
669 // Set retired_committed on nodes for which RETIRED status
670 // has been committed.
671 node_info.retired_committed = true;
672 nodes->put(node_id, node_info);
673
674 LOG_DEBUG_FMT("Setting retired_committed on node {}", node_id);
675 }
676 return true;
677 });
678
679 return make_success();
680 };
681 make_endpoint(
682 "network/nodes/set_retired_committed",
683 HTTP_POST,
684 json_adapter(set_retired_committed),
685 {std::make_shared<NodeCertAuthnPolicy>()})
686 .set_openapi_hidden(true)
687 .install();
688
689 auto get_state = [this](auto& args, nlohmann::json&&) {
690 GetState::Out result;
691 auto [s, rts, lrs] = this->node_operation.state();
692 result.node_id = this->context.get_node_id();
693 result.state = s;
694 result.recovery_target_seqno = rts;
695 result.last_recovered_seqno = lrs;
696 result.startup_seqno =
697 this->node_operation.get_startup_snapshot_seqno();
698
699 // Read last signed seqno from both raw and COSE signature tables
700 auto signatures = args.tx.template ro<Signatures>(Tables::SIGNATURES);
701 auto sig = signatures->get();
702
703 ccf::kv::Version raw_seqno = 0;
704 if (sig.has_value())
705 {
706 raw_seqno = sig.value().seqno;
707 }
708
709 ccf::kv::Version cose_seqno = 0;
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())
714 {
715 auto receipt = ccf::cose::decode_ccf_receipt(cose_sig.value(), false);
716 auto txid = ccf::TxID::from_str(receipt.phdr.ccf.txid);
717 if (!txid.has_value())
718 {
719 throw std::logic_error(fmt::format(
720 "Failed to parse txid from COSE signature: {}",
721 receipt.phdr.ccf.txid));
722 }
723 cose_seqno = txid->seqno;
724 }
725
726 result.last_signed_seqno = std::max(raw_seqno, cose_seqno);
727
728 auto node_configuration_subsystem =
729 this->context.get_subsystem<NodeConfigurationSubsystem>();
730 if (!node_configuration_subsystem)
731 {
732 return make_error(
733 HTTP_STATUS_INTERNAL_SERVER_ERROR,
734 ccf::errors::InternalError,
735 "NodeConfigurationSubsystem is not available");
736 }
737 result.stop_notice =
738 node_configuration_subsystem->has_received_stop_notice();
739
740 return make_success(result);
741 };
742 make_read_only_endpoint(
743 "/state", HTTP_GET, json_read_only_adapter(get_state), no_auth_required)
744 .set_auto_schema<GetState>()
745 .set_forwarding_required(endpoints::ForwardingRequired::Never)
746 .install();
747
748 auto get_quote = [this](auto& args, nlohmann::json&&) {
749 QuoteInfo node_quote_info;
750 const auto result =
751 get_quote_for_this_node_v1(args.tx, node_quote_info);
752 if (result == ApiResult::OK)
753 {
754 Quote q;
755 q.node_id = context.get_node_id();
756 q.raw = node_quote_info.quote;
757 q.endorsements = node_quote_info.endorsements;
758 q.format = node_quote_info.format;
759 q.uvm_endorsements = node_quote_info.uvm_endorsements;
760
761 auto nodes = args.tx.ro(network.nodes);
762 auto node_info = nodes->get(context.get_node_id());
763 if (node_info.has_value() && node_info->code_digest.has_value())
764 {
765 q.measurement = node_info->code_digest.value();
766 }
767 else
768 {
769 auto measurement =
771 if (measurement.has_value())
772 {
773 q.measurement = measurement.value().hex_str();
774 }
775 else
776 {
777 return make_error(
778 HTTP_STATUS_INTERNAL_SERVER_ERROR,
779 ccf::errors::InvalidQuote,
780 "Failed to extract code id from node quote.");
781 }
782 }
783
784 return make_success(q);
785 }
786
787 if (result == ApiResult::NotFound)
788 {
789 return make_error(
790 HTTP_STATUS_NOT_FOUND,
791 ccf::errors::ResourceNotFound,
792 "Could not find node quote.");
793 }
794
795 return make_error(
796 HTTP_STATUS_INTERNAL_SERVER_ERROR,
797 ccf::errors::InternalError,
798 fmt::format("Error code: {}", ccf::api_result_to_str(result)));
799 };
800 make_read_only_endpoint(
801 "/quotes/self",
802 HTTP_GET,
803 json_read_only_adapter(get_quote),
804 no_auth_required)
805 .set_auto_schema<void, Quote>()
806 .set_forwarding_required(endpoints::ForwardingRequired::Never)
807 .install();
808 make_read_only_endpoint(
809 "/attestations/self",
810 HTTP_GET,
811 json_read_only_adapter(get_quote),
812 no_auth_required)
813 .set_auto_schema<void, Attestation>()
814 .set_forwarding_required(endpoints::ForwardingRequired::Never)
815 .install();
816
817 auto get_quotes = [this](auto& args, nlohmann::json&&) {
818 GetQuotes::Out result;
819
820 auto nodes = args.tx.ro(network.nodes);
821 nodes->foreach([&quotes = result.quotes](
822 const auto& node_id, const auto& node_info) {
823 if (node_info.status == ccf::NodeStatus::TRUSTED)
824 {
825 Quote q;
826 q.node_id = node_id;
827 q.raw = node_info.quote_info.quote;
828 q.endorsements = node_info.quote_info.endorsements;
829 q.format = node_info.quote_info.format;
830 q.uvm_endorsements = node_info.quote_info.uvm_endorsements;
831
832 if (node_info.code_digest.has_value())
833 {
834 q.measurement = node_info.code_digest.value();
835 }
836 else
837 {
838 auto measurement =
839 AttestationProvider::get_measurement(node_info.quote_info);
840 if (measurement.has_value())
841 {
842 q.measurement = measurement.value().hex_str();
843 }
844 }
845 quotes.emplace_back(q);
846 }
847 return true;
848 });
849
850 return make_success(result);
851 };
852 make_read_only_endpoint(
853 "/quotes",
854 HTTP_GET,
855 json_read_only_adapter(get_quotes),
856 no_auth_required)
857 .set_auto_schema<GetQuotes>()
858 .install();
859
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);
864 if (body != nullptr)
865 {
866 auto result = nlohmann::json::object();
867 result["attestations"] = (*body)["quotes"];
868 return make_success(result);
869 }
870
871 return res;
872 };
873 make_read_only_endpoint(
874 "/attestations",
875 HTTP_GET,
876 json_read_only_adapter(get_attestations),
877 no_auth_required)
878 .set_auto_schema<GetAttestations>()
879 .install();
880
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())
886 {
887 const auto& service_value = service_state.value();
888 out.service_status = service_value.status;
889 out.service_certificate = service_value.cert;
890 out.recovery_count = service_value.recovery_count.value_or(0);
891 out.service_data = service_value.service_data;
893 service_value.current_service_create_txid;
894 if (consensus != nullptr)
895 {
896 out.current_view = consensus->get_view();
897 auto primary_id = consensus->primary();
898 if (primary_id.has_value())
899 {
900 out.primary_id = primary_id.value();
901 }
902 }
903 return make_success(out);
904 }
905 return make_error(
906 HTTP_STATUS_NOT_FOUND,
907 ccf::errors::ResourceNotFound,
908 "Service state not available.");
909 };
910 make_read_only_endpoint(
911 "/network",
912 HTTP_GET,
913 json_read_only_adapter(network_status),
914 no_auth_required)
915 .set_auto_schema<void, GetNetworkInfo::Out>()
916 .install();
917
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();
922 if (psi.has_value())
923 {
925 out.previous_service_identity = psi.value();
926 return make_success(out);
927 }
928
929 return make_error(
930 HTTP_STATUS_NOT_FOUND,
931 ccf::errors::ResourceNotFound,
932 "This service is not a recovery of a previous service.");
933 };
934 make_read_only_endpoint(
935 "/service/previous_identity",
936 HTTP_GET,
937 json_read_only_adapter(service_previous_identity),
938 no_auth_required)
939 .set_auto_schema<void, GetServicePreviousIdentity::Out>()
940 .install();
941
942 auto get_nodes = [this](auto& args, nlohmann::json&&) {
943 const auto parsed_query =
944 http::parse_query(args.rpc_ctx->get_request_query());
945
946 std::string error_string; // Ignored - all params are optional
947 const auto host = http::get_query_value_opt<std::string>(
948 parsed_query, "host", error_string);
949 const auto port = http::get_query_value_opt<std::string>(
950 parsed_query, "port", error_string);
951 const auto status_str = http::get_query_value_opt<std::string>(
952 parsed_query, "status", error_string);
953
954 std::optional<NodeStatus> status;
955 if (status_str.has_value())
956 {
957 // Convert the query argument to a JSON string, try to parse it as
958 // a NodeStatus, return an error if this doesn't work
959 try
960 {
961 status = nlohmann::json(status_str.value()).get<NodeStatus>();
962 }
963 catch (const ccf::JsonParseError& e)
964 {
965 return ccf::make_error(
966 HTTP_STATUS_BAD_REQUEST,
967 ccf::errors::InvalidQueryParameterValue,
968 fmt::format(
969 "Query parameter '{}' is not a valid node status",
970 status_str.value()));
971 }
972 }
973
974 GetNodes::Out out;
975
976 auto nodes = args.tx.ro(this->network.nodes);
977 nodes->foreach([this, host, port, status, &out, nodes](
978 const NodeId& nid, const NodeInfo& ni) {
979 if (status.has_value() && status.value() != ni.status)
980 {
981 return true;
982 }
983
984 // Match on any interface
985 bool is_matched = false;
986 for (auto const& interface : ni.rpc_interfaces)
987 {
988 const auto& [pub_host, pub_port] =
989 split_net_address(interface.second.published_address);
990
991 if (
992 (!host.has_value() || host.value() == pub_host) &&
993 (!port.has_value() || port.value() == pub_port))
994 {
995 is_matched = true;
996 break;
997 }
998 }
999
1000 if (!is_matched)
1001 {
1002 return true;
1003 }
1004
1005 bool is_primary = false;
1006 if (consensus != nullptr)
1007 {
1008 is_primary = consensus->primary() == nid;
1009 }
1010
1011 out.nodes.push_back(
1012 {nid,
1013 ni.status,
1014 is_primary,
1015 ni.rpc_interfaces,
1016 ni.node_data,
1017 nodes->get_version_of_previous_write(nid).value_or(0)});
1018 return true;
1019 });
1020
1021 return make_success(out);
1022 };
1023 make_read_only_endpoint(
1024 "/network/nodes",
1025 HTTP_GET,
1026 json_read_only_adapter(get_nodes),
1027 no_auth_required)
1028 .set_auto_schema<void, GetNodes::Out>()
1029 .add_query_parameter<std::string>(
1031 .add_query_parameter<std::string>(
1033 .add_query_parameter<std::string>(
1035 .install();
1036
1037 auto get_removable_nodes = [this](auto& args, nlohmann::json&&) {
1038 GetNodes::Out out;
1039
1040 auto nodes = args.tx.ro(this->network.nodes);
1041 nodes->foreach(
1042 [&out, nodes](const NodeId& node_id, const NodeInfo& /*ni*/) {
1043 // Only nodes whose retire_committed status is committed can be
1044 // safely removed, because any primary elected from here on would
1045 // consider them retired, and would consequently not need their
1046 // input in any quorum. We must therefore read the KV at its
1047 // globally committed watermark, for the purpose of this RPC. Since
1048 // this transaction does not perform a write, it is safe to do this.
1049 auto node = nodes->get_globally_committed(node_id);
1050 if (
1051 node.has_value() && node->status == ccf::NodeStatus::RETIRED &&
1052 node->retired_committed)
1053 {
1054 out.nodes.push_back(
1055 {node_id,
1056 node->status,
1057 false /* is_primary */,
1058 node->rpc_interfaces,
1059 node->node_data,
1060 nodes->get_version_of_previous_write(node_id).value_or(0)});
1061 }
1062 return true;
1063 });
1064
1065 return make_success(out);
1066 };
1067
1068 make_read_only_endpoint(
1069 "/network/removable_nodes",
1070 HTTP_GET,
1071 json_read_only_adapter(get_removable_nodes),
1072 no_auth_required)
1073 .set_auto_schema<void, GetNodes::Out>()
1074 .install();
1075
1076 auto delete_retired_committed_node = [this](
1077 auto& args, nlohmann::json&&) {
1078 GetNodes::Out out;
1079
1080 std::string node_id;
1081 std::string error;
1082 if (!get_path_param(
1083 args.rpc_ctx->get_request_path_params(),
1084 "node_id",
1085 node_id,
1086 error))
1087 {
1088 return make_error(
1089 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName, error);
1090 }
1091
1092 auto nodes = args.tx.rw(this->network.nodes);
1093 if (!nodes->has(node_id))
1094 {
1095 return make_error(
1096 HTTP_STATUS_NOT_FOUND,
1097 ccf::errors::ResourceNotFound,
1098 "No such node");
1099 }
1100
1101 auto node_endorsed_certificates =
1102 args.tx.rw(network.node_endorsed_certificates);
1103
1104 // A node's retirement is only complete when the
1105 // transition of retired_committed is itself committed,
1106 // i.e. when the next eligible primary is guaranteed to
1107 // be aware the retirement is committed.
1108 // As a result, the handler must check node info at the
1109 // current committed level, rather than at the end of the
1110 // local suffix.
1111 // While this transaction does execute a write, it specifically
1112 // deletes the value it reads from. It is therefore safe to
1113 // execute on the basis of a potentially stale read-set,
1114 // which get_globally_committed() typically produces.
1115 auto node = nodes->get_globally_committed(node_id);
1116 if (
1117 node.has_value() && node->status == ccf::NodeStatus::RETIRED &&
1118 node->retired_committed)
1119 {
1120 nodes->remove(node_id);
1121 node_endorsed_certificates->remove(node_id);
1122
1123 // clean up sealing tables
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(
1128 [&](
1129 const auto& sealing_recovery_name, const auto& sealing_node_id) {
1130 if (sealing_node_id == node_id)
1131 {
1132 local_sealing_node_id_map->remove(sealing_recovery_name);
1133 return false;
1134 }
1135 return true;
1136 });
1137 auto* sealed_recovery_keys = args.tx.template rw<SealedRecoveryKeys>(
1138 Tables::SEALED_RECOVERY_KEYS);
1139 sealed_recovery_keys->remove(node_id);
1140 }
1141 else
1142 {
1143 return make_error(
1144 HTTP_STATUS_BAD_REQUEST,
1145 ccf::errors::NodeNotRetiredCommitted,
1146 "Node is not completely retired");
1147 }
1148
1149 return make_success(true);
1150 };
1151
1152 make_endpoint(
1153 "/network/nodes/{node_id}",
1154 HTTP_DELETE,
1155 json_adapter(delete_retired_committed_node),
1156 no_auth_required)
1157 .set_auto_schema<void, bool>()
1158 .install();
1159
1160 auto get_self_signed_certificate =
1161 [this](auto& /*args*/, nlohmann::json&&) {
1163 this->node_operation.get_self_signed_node_certificate()};
1164 };
1165 make_command_endpoint(
1166 "/self_signed_certificate",
1167 HTTP_GET,
1168 json_command_adapter(get_self_signed_certificate),
1169 no_auth_required)
1170 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1171 .set_auto_schema<void, SelfSignedNodeCertificateInfo>()
1172 .install();
1173
1174 auto get_node_info = [this](auto& args, nlohmann::json&&) {
1175 std::string node_id;
1176 std::string error;
1177 if (!get_path_param(
1178 args.rpc_ctx->get_request_path_params(),
1179 "node_id",
1180 node_id,
1181 error))
1182 {
1183 return make_error(
1184 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName, error);
1185 }
1186
1187 auto nodes = args.tx.ro(this->network.nodes);
1188 auto info = nodes->get(node_id);
1189
1190 if (!info)
1191 {
1192 return make_error(
1193 HTTP_STATUS_NOT_FOUND,
1194 ccf::errors::ResourceNotFound,
1195 "Node not found");
1196 }
1197
1198 bool is_primary = false;
1199 if (consensus != nullptr)
1200 {
1201 auto primary = consensus->primary();
1202 if (primary.has_value() && primary.value() == node_id)
1203 {
1204 is_primary = true;
1205 }
1206 }
1207 auto& ni = info.value();
1209 node_id,
1210 ni.status,
1211 is_primary,
1212 ni.rpc_interfaces,
1213 ni.node_data,
1214 nodes->get_version_of_previous_write(node_id).value_or(0)});
1215 };
1216 make_read_only_endpoint(
1217 "/network/nodes/{node_id}",
1218 HTTP_GET,
1219 json_read_only_adapter(get_node_info),
1220 no_auth_required)
1221 .set_auto_schema<void, GetNode::Out>()
1222 .install();
1223
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);
1228
1229 bool is_primary = false;
1230 if (consensus != nullptr)
1231 {
1232 auto primary = consensus->primary();
1233 if (primary.has_value() && primary.value() == node_id)
1234 {
1235 is_primary = true;
1236 }
1237 }
1238
1239 if (info.has_value())
1240 {
1241 // Answers from the KV are preferred, as they are more up-to-date,
1242 // especially status and node_data.
1243 auto& ni = info.value();
1245 node_id,
1246 ni.status,
1247 is_primary,
1248 ni.rpc_interfaces,
1249 ni.node_data,
1250 nodes->get_version_of_previous_write(node_id).value_or(0)});
1251 }
1252
1253 // If the node isn't in its KV yet, fall back to configuration
1254 auto node_configuration_subsystem =
1255 this->context.get_subsystem<NodeConfigurationSubsystem>();
1256 if (!node_configuration_subsystem)
1257 {
1258 return make_error(
1259 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1260 ccf::errors::InternalError,
1261 "NodeConfigurationSubsystem is not available");
1262 }
1263 const auto& node_startup_config =
1264 node_configuration_subsystem->get().node_config;
1266 node_id,
1268 is_primary,
1269 node_startup_config.network.rpc_interfaces,
1270 node_startup_config.node_data,
1271 0});
1272 };
1273 make_read_only_endpoint(
1274 "/network/nodes/self",
1275 HTTP_GET,
1276 json_read_only_adapter(get_self_node),
1277 no_auth_required)
1278 .set_auto_schema<void, GetNode::Out>()
1279 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1280 .install();
1281
1282 auto get_primary_node = [this](auto& args, nlohmann::json&&) {
1283 if (consensus != nullptr)
1284 {
1285 auto primary_id = consensus->primary();
1286 if (!primary_id.has_value())
1287 {
1288 return make_error(
1289 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1290 ccf::errors::InternalError,
1291 "Primary unknown");
1292 }
1293
1294 auto nodes = args.tx.ro(this->network.nodes);
1295 auto info = nodes->get(primary_id.value());
1296 if (!info)
1297 {
1298 return make_error(
1299 HTTP_STATUS_NOT_FOUND,
1300 ccf::errors::ResourceNotFound,
1301 "Node not found");
1302 }
1303
1304 auto& ni = info.value();
1306 primary_id.value(),
1307 ni.status,
1308 true,
1309 ni.rpc_interfaces,
1310 ni.node_data,
1311 nodes->get_version_of_previous_write(primary_id.value())
1312 .value_or(0)});
1313 }
1314
1315 return make_error(
1316 HTTP_STATUS_NOT_FOUND,
1317 ccf::errors::ResourceNotFound,
1318 "No configured consensus");
1319 };
1320 make_read_only_endpoint(
1321 "/network/nodes/primary",
1322 HTTP_GET,
1323 json_read_only_adapter(get_primary_node),
1324 no_auth_required)
1325 .set_auto_schema<void, GetNode::Out>()
1326 .install();
1327
1328 auto head_primary = [this](auto& args) {
1329 if (this->node_operation.can_replicate())
1330 {
1331 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1332 }
1333 else
1334 {
1335 if (consensus == nullptr)
1336 {
1337 args.rpc_ctx->set_error(
1338 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1339 ccf::errors::InternalError,
1340 "Consensus not initialised");
1341 return;
1342 }
1343
1344 auto primary_id = consensus->primary();
1345 if (!primary_id.has_value())
1346 {
1347 args.rpc_ctx->set_error(
1348 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1349 ccf::errors::InternalError,
1350 "Primary unknown");
1351 return;
1352 }
1353
1354 const auto address = node::get_redirect_address_for_node(
1355 args, args.tx, primary_id.value());
1356 if (!address.has_value())
1357 {
1358 return;
1359 }
1360
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);
1365 }
1366 };
1367 make_read_only_endpoint(
1368 "/primary", HTTP_HEAD, head_primary, no_auth_required)
1369 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1370 .install();
1371
1372 auto get_primary = [this](auto& args) {
1373 if (this->node_operation.can_replicate())
1374 {
1375 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1376 return;
1377 }
1378
1379 args.rpc_ctx->set_error(
1380 HTTP_STATUS_NOT_FOUND,
1381 ccf::errors::ResourceNotFound,
1382 "Node is not primary");
1383 };
1384 make_read_only_endpoint(
1385 "/primary", HTTP_GET, get_primary, no_auth_required)
1386 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1387 .install();
1388
1389 auto get_backup = [this](auto& args) {
1390 if (!this->node_operation.can_replicate())
1391 {
1392 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1393 return;
1394 }
1395
1396 args.rpc_ctx->set_error(
1397 HTTP_STATUS_NOT_FOUND,
1398 ccf::errors::ResourceNotFound,
1399 "Node is not backup");
1400 };
1401 make_read_only_endpoint("/backup", HTTP_GET, get_backup, no_auth_required)
1402 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1403 .install();
1404
1405 auto consensus_config = [this](auto& /*args*/, nlohmann::json&&) {
1406 // Query node for configurations, separate current from pending
1407 if (consensus != nullptr)
1408 {
1409 auto cfg = consensus->get_latest_configuration();
1410 ConsensusConfig cc;
1411 for (auto& [nid, ninfo] : cfg)
1412 {
1413 cc.emplace(
1414 nid.value(),
1416 fmt::format("{}:{}", ninfo.hostname, ninfo.port)});
1417 }
1418 return make_success(cc);
1419 }
1420
1421 return make_error(
1422 HTTP_STATUS_NOT_FOUND,
1423 ccf::errors::ResourceNotFound,
1424 "No configured consensus");
1425 };
1426
1427 make_command_endpoint(
1428 "/config",
1429 HTTP_GET,
1430 json_command_adapter(consensus_config),
1431 no_auth_required)
1432 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1433 .set_auto_schema<void, ConsensusConfig>()
1434 .install();
1435
1436 auto consensus_state = [this](auto& /*args*/, nlohmann::json&&) {
1437 if (consensus != nullptr)
1438 {
1439 return make_success(ConsensusConfigDetails{consensus->get_details()});
1440 }
1441
1442 return make_error(
1443 HTTP_STATUS_NOT_FOUND,
1444 ccf::errors::ResourceNotFound,
1445 "No configured consensus");
1446 };
1447
1448 make_command_endpoint(
1449 "/consensus",
1450 HTTP_GET,
1451 json_command_adapter(consensus_state),
1452 no_auth_required)
1453 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1454 .set_auto_schema<void, ConsensusConfigDetails>()
1455 .install();
1456
1457 auto node_metrics = [this](auto& args) {
1458 NodeMetrics nm;
1459 nm.sessions = node_operation.get_session_metrics();
1460
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());
1465 };
1466
1467 make_command_endpoint(
1468 "/metrics", HTTP_GET, node_metrics, no_auth_required)
1469 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1470 .set_auto_schema<void, NodeMetrics>()
1471 .install();
1472
1473 auto js_metrics = [this](auto& args, nlohmann::json&&) {
1474 auto bytecode_map = args.tx.ro(this->network.modules_quickjs_bytecode);
1475 auto version_val = args.tx.ro(this->network.modules_quickjs_version);
1476 uint64_t bytecode_size = 0;
1477 bytecode_map->foreach(
1478 [&bytecode_size](const auto&, const auto& bytecode) {
1479 bytecode_size += bytecode.size();
1480 return true;
1481 });
1482 auto js_engine_map = args.tx.ro(this->network.js_engine);
1484 m.bytecode_size = bytecode_size;
1485 m.bytecode_used =
1486 version_val->get() == std::string(ccf::quickjs_version);
1487
1488 auto options = js_engine_map->get().value_or(ccf::JSRuntimeOptions{});
1489 m.max_stack_size = options.max_stack_bytes;
1490 m.max_heap_size = options.max_heap_bytes;
1491 m.max_execution_time = options.max_execution_time_ms;
1492 m.max_cached_interpreters = options.max_cached_interpreters;
1493
1494 return m;
1495 };
1496
1497 make_read_only_endpoint(
1498 "/js_metrics",
1499 HTTP_GET,
1500 json_read_only_adapter(js_metrics),
1501 no_auth_required)
1502 .set_auto_schema<void, JavaScriptMetrics>()
1503 .install();
1504
1505 auto version = [](auto&, nlohmann::json&&) {
1506 GetVersion::Out result;
1507 result.ccf_version = ccf::ccf_version;
1508 result.quickjs_version = ccf::quickjs_version;
1509 result.unsafe = false;
1510
1511 return make_success(result);
1512 };
1513
1514 make_command_endpoint(
1515 "/version", HTTP_GET, json_command_adapter(version), no_auth_required)
1516 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1517 .set_auto_schema<GetVersion>()
1518 .install();
1519
1520 auto create = [this](auto& ctx, nlohmann::json&& params) {
1521 LOG_INFO_FMT("Processing create RPC");
1522
1523 bool recovering = node_operation.is_reading_public_ledger();
1524
1525 // This endpoint can only be called once, directly from the starting
1526 // node for the genesis or end of public recovery transaction to
1527 // initialise the service
1528 if (!node_operation.is_in_initialised_state() && !recovering)
1529 {
1530 return make_error(
1531 HTTP_STATUS_FORBIDDEN,
1532 ccf::errors::InternalError,
1533 "Node is not in initial state.");
1534 }
1535
1536 const auto& sig_auth_ident =
1537 ctx.template get_caller<ccf::AnyCertAuthnIdentity>();
1538 // AnyCertAuthnIdentity requires DER format certificates
1539 const auto caller_node_id =
1540 compute_node_id_from_cert_der(sig_auth_ident.cert);
1541 if (caller_node_id != this->context.get_node_id())
1542 {
1543 return make_error(
1544 HTTP_STATUS_FORBIDDEN,
1545 ccf::errors::AuthorizationFailed,
1546 "Only the node itself can call this endpoint.");
1547 }
1548
1549 const auto in = params.get<CreateNetworkNodeToNode::In>();
1550
1551 if (InternalTablesAccess::is_service_created(ctx.tx, in.service_cert))
1552 {
1553 return make_error(
1554 HTTP_STATUS_FORBIDDEN,
1555 ccf::errors::InternalError,
1556 "Service is already created.");
1557 }
1558
1560 ctx.tx, in.service_cert, in.create_txid, in.service_data, recovering);
1561
1562 // Retire all nodes, in case there are any (i.e. post recovery)
1564
1565 // Genesis transaction (i.e. not after recovery)
1566 if (in.genesis_info.has_value())
1567 {
1568 // Note that it is acceptable to start a network without any member
1569 // having a recovery share. The service will check that at least one
1570 // recovery member is added before the service is opened.
1571 for (const auto& info : in.genesis_info->members)
1572 {
1574 }
1575
1577 ctx.tx, in.genesis_info->service_configuration);
1579 ctx.tx, in.genesis_info->constitution);
1580 }
1581 else
1582 {
1583 // On recovery, force a new ledger chunk
1584 auto* tx_ = static_cast<ccf::kv::CommittableTx*>(&ctx.tx);
1585 if (tx_ == nullptr)
1586 {
1587 throw std::logic_error("Could not cast tx to CommittableTx");
1588 }
1589 tx_->set_tx_flag(
1591 }
1592
1593 auto endorsed_certificates =
1594 ctx.tx.rw(network.node_endorsed_certificates);
1595 endorsed_certificates->put(in.node_id, in.node_endorsed_certificate);
1596
1597 NodeInfo node_info = {
1598 in.node_info_network,
1599 {in.quote_info},
1600 in.public_encryption_key,
1602 std::nullopt,
1603 in.measurement.hex_str(),
1604 in.certificate_signing_request,
1605 in.public_key,
1606 in.node_data};
1607 InternalTablesAccess::add_node(ctx.tx, in.node_id, node_info);
1608
1609 if (in.sealing_recovery_data.has_value())
1610 {
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);
1616
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);
1621 }
1622
1623 node_operation.shuffle_sealed_shares(ctx.tx);
1624
1625 if (
1626 in.quote_info.format != QuoteFormat::amd_sev_snp_v1 ||
1627 !in.snp_uvm_endorsements.has_value())
1628 {
1629 // For improved serviceability on SNP, do not record trusted
1630 // measurements if UVM endorsements are available
1632 ctx.tx, in.measurement, in.quote_info.format);
1633 }
1634
1635 switch (in.quote_info.format)
1636 {
1638 {
1639 auto host_data = AttestationProvider::get_host_data(in.quote_info);
1640 if (host_data.has_value())
1641 {
1643 ctx.tx, host_data.value());
1644 }
1645 else
1646 {
1647 LOG_FAIL_FMT("Unable to extract host data from virtual quote");
1648 }
1649 break;
1650 }
1651
1653 {
1654 auto host_data =
1655 AttestationProvider::get_host_data(in.quote_info).value();
1657 ctx.tx, host_data, in.snp_security_policy);
1658
1660 ctx.tx, in.snp_uvm_endorsements, recovering);
1661
1662 auto attestation =
1663 AttestationProvider::get_snp_attestation(in.quote_info).value();
1665 ctx.tx, attestation);
1666 break;
1667 }
1669 {
1670 break;
1671 }
1672 }
1673
1674 std::optional<ccf::ClaimsDigest::Digest> digest =
1676 if (digest.has_value())
1677 {
1678 auto digest_value = digest.value();
1679 ctx.rpc_ctx->set_claims_digest(std::move(digest_value));
1680 }
1681
1682 this->node_operation.recovery_decision_protocol().reset_state(ctx.tx);
1683 this->node_operation.recovery_decision_protocol().try_start(
1684 ctx.tx, recovering);
1685
1686 LOG_INFO_FMT("Created service");
1687 return make_success(true);
1688 };
1689 make_endpoint(
1690 "/create",
1691 HTTP_POST,
1692 json_adapter(create),
1693 {std::make_shared<AnyCertAuthnPolicy>()})
1694 .set_openapi_hidden(true)
1695 .install();
1696
1697 // Only called from node. See node_state.h.
1698 auto refresh_jwt_keys = [this](auto& ctx, nlohmann::json&& body) {
1699 // All errors are server errors since the client is the server.
1700
1701 auto primary_id = consensus->primary();
1702 if (!primary_id.has_value())
1703 {
1704 LOG_FAIL_FMT("JWT key auto-refresh: primary unknown");
1705 return make_error(
1706 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1707 ccf::errors::InternalError,
1708 "Primary is unknown");
1709 }
1710
1711 const auto& sig_auth_ident =
1712 ctx.template get_caller<ccf::NodeCertAuthnIdentity>();
1713 if (primary_id.value() != sig_auth_ident.node_id)
1714 {
1716 "JWT key auto-refresh: request does not originate from primary");
1717 return make_error(
1718 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1719 ccf::errors::InternalError,
1720 "Request does not originate from primary.");
1721 }
1722
1724 try
1725 {
1726 parsed = body.get<SetJwtPublicSigningKeys>();
1727 }
1728 catch (const ccf::JsonParseError& e)
1729 {
1730 return make_error(
1731 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1732 ccf::errors::InternalError,
1733 "Unable to parse body.");
1734 }
1735
1736 auto issuers = ctx.tx.ro(this->network.jwt_issuers);
1737 auto issuer_metadata_ = issuers->get(parsed.issuer);
1738 if (!issuer_metadata_.has_value())
1739 {
1741 "JWT key auto-refresh: {} is not a valid issuer", parsed.issuer);
1742 return make_error(
1743 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1744 ccf::errors::InternalError,
1745 fmt::format("{} is not a valid issuer.", parsed.issuer));
1746 }
1747 auto& issuer_metadata = issuer_metadata_.value();
1748
1749 if (!issuer_metadata.auto_refresh)
1750 {
1752 "JWT key auto-refresh: {} does not have auto_refresh enabled",
1753 parsed.issuer);
1754 return make_error(
1755 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1756 ccf::errors::InternalError,
1757 fmt::format(
1758 "{} does not have auto_refresh enabled.", parsed.issuer));
1759 }
1760
1761 if (!set_jwt_public_signing_keys(
1762 ctx.tx,
1763 "<auto-refresh>",
1764 parsed.issuer,
1765 issuer_metadata,
1766 parsed.jwks))
1767 {
1769 "JWT key auto-refresh: error while storing signing keys for issuer "
1770 "{}",
1771 parsed.issuer);
1772 return make_error(
1773 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1774 ccf::errors::InternalError,
1775 fmt::format(
1776 "Error while storing signing keys for issuer {}.",
1777 parsed.issuer));
1778 }
1779
1780 return make_success(true);
1781 };
1782 make_endpoint(
1783 "/jwt_keys/refresh",
1784 HTTP_POST,
1785 json_adapter(refresh_jwt_keys),
1786 {std::make_shared<NodeCertAuthnPolicy>()})
1787 .set_openapi_hidden(true)
1788 .install();
1789
1790 auto get_jwt_metrics =
1791 [this](auto& /*args*/, const nlohmann::json& /*params*/) {
1792 return make_success(jwt_refresh_metrics);
1793 };
1794 make_read_only_endpoint(
1795 "/jwt_keys/refresh/metrics",
1796 HTTP_GET,
1797 json_read_only_adapter(get_jwt_metrics),
1798 no_auth_required)
1799 .set_auto_schema<void, JWTRefreshMetrics>()
1800 .install();
1801
1802 auto service_config_handler =
1803 [this](auto& args, const nlohmann::json& /*params*/) {
1804 return make_success(args.tx.ro(network.config)->get());
1805 };
1806 make_endpoint(
1807 "/service/configuration",
1808 HTTP_GET,
1809 json_adapter(service_config_handler),
1810 no_auth_required)
1811 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1812 .set_auto_schema<void, ServiceConfiguration>()
1813 .install();
1814
1815 auto list_indexing_strategies = [this](
1816 auto& /*args*/,
1817 const nlohmann::json& /*params*/) {
1818 return make_success(this->context.get_indexing_strategies().describe());
1819 };
1820
1821 make_endpoint(
1822 "/index/strategies",
1823 HTTP_GET,
1824 json_adapter(list_indexing_strategies),
1825 no_auth_required)
1826 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1827 .set_auto_schema<void, nlohmann::json>()
1828 .install();
1829
1830 auto get_ready_app =
1831 [this](const ccf::endpoints::ReadOnlyEndpointContext& ctx) {
1832 auto node_configuration_subsystem =
1833 this->context.get_subsystem<NodeConfigurationSubsystem>();
1834 if (!node_configuration_subsystem)
1835 {
1836 ctx.rpc_ctx->set_error(
1837 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1838 ccf::errors::InternalError,
1839 "NodeConfigurationSubsystem is not available");
1840 return;
1841 }
1842 if (
1843 !node_configuration_subsystem->has_received_stop_notice() &&
1844 this->node_operation.is_part_of_network() &&
1845 this->node_operation.is_user_frontend_open())
1846 {
1847 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1848 }
1849 else
1850 {
1851 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1852 }
1853 return;
1854 };
1855 make_read_only_endpoint(
1856 "/ready/app", HTTP_GET, get_ready_app, no_auth_required)
1857 .set_auto_schema<void, void>()
1858 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1859 .install();
1860
1861 auto get_ready_gov =
1862 [this](const ccf::endpoints::ReadOnlyEndpointContext& ctx) {
1863 auto node_configuration_subsystem =
1864 this->context.get_subsystem<NodeConfigurationSubsystem>();
1865 if (!node_configuration_subsystem)
1866 {
1867 ctx.rpc_ctx->set_error(
1868 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1869 ccf::errors::InternalError,
1870 "NodeConfigurationSubsystem is not available");
1871 return;
1872 }
1873 if (
1874 !node_configuration_subsystem->has_received_stop_notice() &&
1875 this->node_operation.is_accessible_to_members() &&
1876 this->node_operation.is_member_frontend_open())
1877 {
1878 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1879 }
1880 else
1881 {
1882 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1883 }
1884 return;
1885 };
1886 make_read_only_endpoint(
1887 "/ready/gov", HTTP_GET, get_ready_gov, no_auth_required)
1888 .set_auto_schema<void, void>()
1889 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1890 .install();
1891
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();
1896 this->node_operation.trigger_snapshot(args.tx);
1897 return make_success();
1898 };
1899 make_endpoint(
1900 "/snapshot:create",
1901 HTTP_POST,
1902 json_adapter(create_snapshot),
1903 no_auth_required)
1904 .set_auto_schema<void, void>()
1905 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1906 .require_operator_feature(endpoints::OperatorFeature::SnapshotCreate)
1907 .install();
1908
1909 ccf::node::init_recovery_decision_protocol_handlers(*this, context);
1910
1911 ccf::node::init_file_serving_handlers(*this, context);
1912
1913 auto historical_cache_info = [this](
1914 [[maybe_unused]] auto& args,
1915 [[maybe_unused]] nlohmann::json&&) {
1917 result.estimated_size =
1918 this->context.get_historical_state().get_estimated_store_cache_size();
1919 return make_success(result);
1920 };
1921 make_read_only_endpoint(
1922 "/historical_cache",
1923 HTTP_GET,
1924 json_read_only_adapter(historical_cache_info),
1925 no_auth_required)
1926 .set_auto_schema<GetHistoricalCacheInfo>()
1927 .install();
1928 }
1929 };
1930
1932 {
1933 protected:
1935
1936 public:
1938 RpcFrontend(*network.tables, node_endpoints, context),
1939 node_endpoints(network, context)
1940 {}
1941 };
1942}
Definition node_operation_interface.h:24
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 void trigger_snapshot(ccf::kv::Tx &tx)=0
virtual bool is_in_initialised_state() const =0
virtual ccf::crypto::Pem get_self_signed_node_certificate()=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, const std::optional< std::vector< uint8_t > > &code_transparent_statement, std::shared_ptr< NetworkIdentitySubsystemInterface > network_identity_subsystem)=0
virtual bool is_reading_private_ledger() const =0
virtual ExtendedState state()=0
virtual RecoveryDecisionProtocolSubsystem & recovery_decision_protocol()=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 void shuffle_sealed_shares(ccf::kv::Tx &tx)=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:152
static std::optional< HostData > get_host_data(const QuoteInfo &quote_info)
Definition quote.cpp:175
static std::optional< pal::PlatformAttestationMeasurement > get_measurement(const QuoteInfo &quote_info)
Definition quote.cpp:135
Definition common_endpoint_registry.h:16
void init_handlers() override
Definition common_endpoint_registry.cpp:70
static bool is_service_created(ccf::kv::ReadOnlyTx &tx, const ccf::crypto::Pem &expected_service_cert)
Definition internal_tables_access.h:536
static void trust_node_uvm_endorsements(ccf::kv::Tx &tx, const std::optional< pal::UVMEndorsements > &uvm_endorsements, bool recovering)
Definition internal_tables_access.h:854
static void init_configuration(ccf::kv::Tx &tx, const ServiceConfiguration &configuration)
Definition internal_tables_access.h:956
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:834
static MemberId add_member(ccf::kv::Tx &tx, const NewMember &member_pub_info)
Definition internal_tables_access.h:187
static void set_constitution(ccf::kv::Tx &tx, const std::string &constitution)
Definition internal_tables_access.h:780
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:470
static void trust_node_measurement(ccf::kv::Tx &tx, const pal::PlatformAttestationMeasurement &node_measurement, const QuoteFormat &platform)
Definition internal_tables_access.h:786
static void retire_active_nodes(ccf::kv::Tx &tx)
Definition internal_tables_access.h:58
static void add_node(ccf::kv::Tx &tx, const NodeId &id, const NodeInfo &node_info)
Definition internal_tables_access.h:434
static void trust_node_virtual_host_data(ccf::kv::Tx &tx, const HostData &host_data)
Definition internal_tables_access.h:826
static void trust_node_snp_tcb_version(ccf::kv::Tx &tx, pal::snp::Attestation &attestation)
Definition internal_tables_access.h:924
Definition json.h:26
Definition node_configuration_subsystem.h:13
const NodeConfigurationState & get() override
Definition node_configuration_subsystem.h:31
Definition node_frontend.h:192
void init_handlers() override
Definition node_frontend.h:450
bool apply_uncommitted_tx_backpressure() const override
Definition node_frontend.h:196
NodeEndpoints(NetworkState &network_, ccf::AbstractNodeContext &context_)
Definition node_frontend.h:438
Definition node_frontend.h:1932
NodeEndpoints node_endpoints
Definition node_frontend.h:1934
NodeRpcFrontend(NetworkState &network, ccf::AbstractNodeContext &context)
Definition node_frontend.h:1937
void try_start(ccf::kv::Tx &tx, bool recovering)
Definition recovery_decision_protocol.cpp:54
void reset_state(ccf::kv::Tx &tx)
Definition recovery_decision_protocol.cpp:28
Definition frontend.h:35
Definition pem.h:18
Definition committable_tx.h:19
M::ReadOnlyHandle * ro(M &m)
Definition tx.h:168
Definition tx.h:200
M::Handle * rw(M &m)
Definition tx.h:211
#define LOG_INFO_FMT
Definition internal_logger.h:15
#define LOG_DEBUG_FMT
Definition internal_logger.h:14
#define LOG_FAIL_FMT
Definition internal_logger.h:16
#define DECLARE_JSON_TYPE_WITH_BASE(TYPE, BASE)
Definition json.h:687
#define DECLARE_JSON_REQUIRED_FIELDS(TYPE,...)
Definition json.h:736
#define DECLARE_JSON_TYPE(TYPE)
Definition json.h:685
#define DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(TYPE)
Definition json.h:712
#define DECLARE_JSON_OPTIONAL_FIELDS(TYPE,...)
Definition json.h:811
std::string error_string(unsigned long ec)
Returns the error string from an error code.
Definition openssl_wrappers.h:33
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:123
uint64_t Version
Definition version.h:10
Definition app_interface.h:13
EntityId< NodeIdFormatter > NodeId
Definition entity_id.h:164
ServiceMap< NodeId, ccf::SealedRecoveryKey > SealedRecoveryKeys
Definition local_sealing.h:43
constexpr auto get_actor_prefix(ActorsType at)
Definition actors.h:24
constexpr char const * api_result_to_str(ApiResult result)
Definition base_endpoint_registry.h:35
NodeId compute_node_id_from_cert_der(const std::vector< uint8_t > &node_cert_der)
Definition nodes.h:36
jsonhandler::JsonAdapterResponse already_populated_response()
Definition json_handler.cpp:137
NodeStatus
Definition node_info.h:18
std::optional< ccf::ClaimsDigest::Digest > get_create_tx_claims_digest(ccf::kv::ReadOnlyTx &tx)
Definition create_tx_claims_digest.cpp:10
QuoteFormat
Definition quote_info.h:12
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:108
endpoints::CommandEndpointFunction json_command_adapter(const CommandHandlerWithJson &f)
Definition json_handler.cpp:159
ServiceStatus
Definition service.h:13
LedgerSignMode get_ledger_sign_mode()
Definition get_ledger_sign_mode_cose.cpp:8
std::map< std::string, ConsensusNodeConfig > ConsensusConfig
Definition node_frontend.h:160
QuoteVerificationResult
Definition quote.h:19
ServiceMap< sealing_recovery::Name, NodeId > LocalSealingNodeIdMap
Definition local_sealing.h:45
ActorsType
Definition actors.h:11
endpoints::ReadOnlyEndpointFunction json_read_only_adapter(const ReadOnlyHandlerWithJson &f)
Definition json_handler.cpp:150
endpoints::EndpointFunction json_adapter(const HandlerJsonParamsAndForward &f)
Definition json_handler.cpp:142
jsonhandler::JsonAdapterResponse make_error(ccf::http_status status, const std::string &code, const std::string &msg)
Definition json_handler.cpp:124
@ error
Definition tls_session.h:23
Definition consensus_types.h:23
Definition configuration.h:14
Message message(uint64_t header)
Definition ring_buffer.h:160
std::optional< fs::path > find_latest_committed_snapshot_in_directory(const fs::path &directory, std::optional< size_t > minimum_idx=std::nullopt)
Definition filenames.h:238
Definition node_context.h:12
Definition node_frontend.h:61
Snapshots snapshots
Definition startup_config.h:118
Definition node_frontend.h:163
ccf::kv::ConsensusDetails details
Definition node_frontend.h:164
Definition node_frontend.h:153
std::string address
Definition node_frontend.h:154
Definition node_call_types.h:60
Definition node_frontend.h:83
std::vector< Attestation > attestations
Definition node_frontend.h:84
Definition node_frontend.h:79
void In
Definition node_frontend.h:80
Definition node_frontend.h:104
size_t estimated_size
Definition node_frontend.h:105
Definition node_frontend.h:100
void In
Definition node_frontend.h:101
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:70
std::vector< Quote > quotes
Definition node_frontend.h:71
Definition node_frontend.h:66
void In
Definition node_frontend.h:67
Definition node_frontend.h:182
ccf::crypto::Pem previous_service_identity
Definition node_frontend.h:183
Definition node_frontend.h:180
Definition node_call_types.h:31
std::optional< ccf::kv::Version > last_recovered_seqno
Definition node_call_types.h:39
std::optional< ccf::kv::Version > recovery_target_seqno
Definition node_call_types.h:38
bool stop_notice
Definition node_call_types.h:41
ccf::NodeId node_id
Definition node_call_types.h:32
ccf::kv::Version startup_seqno
Definition node_call_types.h:35
ccf::NodeStartupState state
Definition node_call_types.h:33
ccf::kv::Version last_signed_seqno
Definition node_call_types.h:34
Definition node_call_types.h:27
Definition node_call_types.h:50
bool unsafe
Definition node_call_types.h:53
std::string quickjs_version
Definition node_call_types.h:52
std::string ccf_version
Definition node_call_types.h:51
Definition node_call_types.h:46
Definition jsengine.h:12
Definition node_frontend.h:133
size_t failures
Definition node_frontend.h:136
size_t attempts
Definition node_frontend.h:134
size_t successes
Definition node_frontend.h:135
Definition node_frontend.h:113
bool bytecode_used
Definition node_frontend.h:115
uint64_t max_execution_time
Definition node_frontend.h:118
uint64_t max_heap_size
Definition node_frontend.h:116
uint64_t max_stack_size
Definition node_frontend.h:117
uint64_t max_cached_interpreters
Definition node_frontend.h:119
uint64_t bytecode_size
Definition node_frontend.h:114
Definition node_call_types.h:87
Definition node_call_types.h:113
Definition node_call_types.h:106
std::optional< NodeId > node_id
Definition node_call_types.h:110
std::optional< NetworkInfo > network_info
Definition node_call_types.h:161
NodeStatus node_status
Definition node_call_types.h:107
Definition jwt.h:77
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:164
const NodeEndorsedCertificates node_endorsed_certificates
Definition network_tables.h:87
const Service service
Definition network_tables.h:177
const Configuration config
Definition network_tables.h:185
const JSEngine js_engine
Definition network_tables.h:146
const ModulesQuickJsBytecode modules_quickjs_bytecode
Definition network_tables.h:141
const ModulesQuickJsVersion modules_quickjs_version
Definition network_tables.h:143
const Nodes nodes
Definition network_tables.h:86
const ccf::StartupConfig & node_config
Definition node_configuration_interface.h:16
RpcInterfaces rpc_interfaces
RPC interfaces.
Definition node_info_network.h:151
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:92
ccf::SessionMetrics sessions
Definition node_frontend.h:93
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:44
std::vector< uint8_t > raw
Definition node_frontend.h:46
std::optional< std::vector< uint8_t > > uvm_endorsements
Definition node_frontend.h:52
std::string measurement
Definition node_frontend.h:50
NodeId node_id
Definition node_frontend.h:45
QuoteFormat format
Definition node_frontend.h:48
std::vector< uint8_t > endorsements
Definition node_frontend.h:47
Definition node_frontend.h:171
ccf::crypto::Pem self_signed_certificate
Definition node_frontend.h:172
Definition service_config.h:14
Definition session_metrics.h:13
Definition node_frontend.h:144
JsonWebKeySet jwks
Definition node_frontend.h:146
std::string issuer
Definition node_frontend.h:145
static std::optional< TxID > from_str(const std::string_view &sv)
Definition tx_id.h:53
Definition endpoint_context.h:86
Definition endpoint_registry.h:48
std::string dispatch_path
Definition endpoint_registry.h:54
std::string method
Definition endpoint_registry.h:49
Definition kv_types.h:154