CCF
Loading...
Searching...
No Matches
node_state.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
6#include "ccf/crypto/pem.h"
9#include "ccf/ds/json.h"
10#include "ccf/entity_id.h"
11#include "ccf/js/core/context.h"
14#include "ccf/pal/locking.h"
15#include "ccf/pal/platform.h"
16#include "ccf/pal/snp_ioctl.h"
22#include "ccf/tx.h"
23#include "consensus/aft/raft.h"
25#include "crypto/certs.h"
26#include "ds/ccf_assert.h"
27#include "ds/files.h"
28#include "ds/internal_logger.h"
29#include "ds/state_machine.h"
31#include "encryptor.h"
32#include "history.h"
33#include "http/http_parser.h"
34#include "indexing/indexer.h"
35#include "js/global_class_ids.h"
36#include "network_state.h"
38#include "node/hooks.h"
41#include "node/ledger_secret.h"
42#include "node/ledger_secrets.h"
43#include "node/local_sealing.h"
47#include "node/snapshotter.h"
48#include "node_to_node.h"
51#include "rpc/frontend.h"
52#include "rpc/serialization.h"
53#include "secret_broadcast.h"
57#include "share_manager.h"
58#include "snapshots/fetch.h"
59#include "snapshots/filenames.h"
60#include "uvm_endorsements.h"
61
62#include <optional>
63
64#ifdef USE_NULL_ENCRYPTOR
65# include "kv/test/null_encryptor.h"
66#endif
67
68#include <algorithm>
69#include <atomic>
70#include <chrono>
71#include <limits>
72#define FMT_HEADER_ONLY
73#include <fmt/format.h>
74#include <nlohmann/json.hpp>
75#include <stdexcept>
76#include <unordered_set>
77#include <vector>
78
79namespace ccf
80{
82
88
89 inline void reset_data(std::vector<uint8_t>& data)
90 {
91 data.clear();
92 data.shrink_to_fit();
93 }
94
95 // Resolve the latest signature view from both signature tables.
96 // In a mixed dual/COSE-only ledger, the traditional signature table may
97 // contain a stale entry from before the switch to COSE-only. Always pick
98 // the signature at the higher seqno.
100 {
101 ccf::kv::Version best_seqno = 0;
102 ccf::kv::Term best_view = 0;
103
104 auto ls = tx.ro<ccf::Signatures>(Tables::SIGNATURES)->get();
105 if (ls.has_value())
106 {
107 best_seqno = ls->seqno;
108 best_view = ls->view;
109 }
110
111 auto lcs = tx.ro<ccf::CoseSignatures>(Tables::COSE_SIGNATURES)->get();
112 if (lcs.has_value())
113 {
114 auto receipt = cose::decode_ccf_receipt(lcs.value(), false);
115 auto tx_id_opt = ccf::TxID::from_str(receipt.phdr.ccf.txid);
116 if (!tx_id_opt.has_value())
117 {
118 throw std::logic_error(fmt::format(
119 "Failed to parse TxID from COSE signature: {}",
120 receipt.phdr.ccf.txid));
121 }
122 if (tx_id_opt->seqno > best_seqno)
123 {
124 best_seqno = tx_id_opt->seqno;
125 best_view = tx_id_opt->view;
126 }
127 }
128
129 if (best_seqno == 0)
130 {
131 throw std::logic_error("No signature found");
132 }
133
134 return best_view;
135 }
136
138 {
140
141 struct FetchSnapshot : public ccf::tasks::BaseTask
142 {
143 const ccf::StartupConfig::Join join_config;
144 const ccf::CCFConfig::Snapshots snapshot_config;
145 NodeState* owner;
146
147 FetchSnapshot(
148 ccf::StartupConfig::Join join_config_,
149 ccf::CCFConfig::Snapshots snapshot_config_,
150 NodeState* owner_) :
151 join_config(std::move(join_config_)),
152 snapshot_config(std::move(snapshot_config_)),
153 owner(owner_)
154 {}
155
156 void do_task_implementation() override
157 {
158 // NB: Eventually this shouldn't be blocking, but reusing the current
159 // (blocking) helper for now
160 auto latest_peer_snapshot = snapshots::fetch_from_peer(
161 join_config.target_rpc_address,
162 join_config.service_cert,
163 join_config.fetch_snapshot_max_attempts,
166
167 // Ensure the in-flight task reference is cleared when this
168 // task completes, regardless of outcome, so that a subsequent
169 // join-retry can schedule a new fetch if needed.
170 struct ClearOnExit
171 {
172 NodeState* owner;
173 ~ClearOnExit()
174 {
175 std::lock_guard<pal::Mutex> guard(owner->lock);
176 owner->snapshot_fetch_task = nullptr;
177 }
178 } clear_on_exit{owner};
179
180 if (latest_peer_snapshot.has_value())
181 {
183 "Received snapshot {} from peer (size: {})",
184 latest_peer_snapshot->snapshot_name,
185 latest_peer_snapshot->snapshot_data.size());
186
187 try
188 {
189 const auto segments =
190 separate_segments(latest_peer_snapshot->snapshot_data);
191 verify_snapshot(segments, join_config.service_cert);
192 }
193 catch (const std::exception& e)
194 {
196 "Error while verifying fetched snapshot {}: {}",
197 latest_peer_snapshot->snapshot_name,
198 e.what());
199
200 return;
201 }
202
203 const auto snapshot_path =
204 std::filesystem::path(latest_peer_snapshot->snapshot_name);
205
206 // Ensure snapshot name is a simple filename (no directories, no "..",
207 // not absolute) before using it as a filesystem path.
208 if (
209 snapshot_path.empty() || snapshot_path.is_absolute() ||
210 snapshot_path.has_parent_path() ||
211 snapshot_path.filename() != snapshot_path)
212 {
214 "Rejecting snapshot with invalid name '{}' from peer",
215 latest_peer_snapshot->snapshot_name);
216 return;
217 }
218 const auto dst_path =
219 std::filesystem::path(snapshot_config.directory) / snapshot_path;
220
222 "Snapshot verified - now writing to {}", dst_path.string());
223
224 if (files::exists(dst_path))
225 {
227 "Overwriting existing snapshot at {} with data retrieved from "
228 "peer",
229 dst_path);
230 }
231 files::dump(latest_peer_snapshot->snapshot_data, dst_path);
232
233 const auto snapshot_seqno =
234 snapshots::get_snapshot_idx_from_file_name(
235 latest_peer_snapshot->snapshot_name);
236
237 std::lock_guard<pal::Mutex> guard(owner->lock);
238 owner->set_startup_snapshot(
239 snapshot_seqno, std::move(latest_peer_snapshot->snapshot_data));
240 }
241 }
242
243 [[nodiscard]] const std::string& get_name() const override
244 {
245 static const std::string name = "FetchSnapshot";
246 return name;
247 }
248 };
249
250 struct BackupSnapshotFetch : public ccf::tasks::BaseTask
251 {
252 const ccf::CCFConfig::Snapshots snapshot_config;
253 ccf::kv::Version since_seqno;
254 NodeState* owner;
255
256 BackupSnapshotFetch(
257 ccf::CCFConfig::Snapshots snapshot_config_,
258 ccf::kv::Version since_seqno_,
259 NodeState* owner_) :
260 snapshot_config(std::move(snapshot_config_)),
261 since_seqno(since_seqno_),
262 owner(owner_)
263 {}
264
265 void do_task_implementation() override
266 {
267 struct ClearOnExit
268 {
269 NodeState* owner;
270 ~ClearOnExit()
271 {
272 std::lock_guard<pal::Mutex> guard(owner->lock);
273 owner->backup_snapshot_fetch_task = nullptr;
274 }
275 } clear_on_exit{owner};
276
277 // Resolve the primary's RPC address
278 std::string primary_address;
279 std::vector<uint8_t> service_cert;
280 {
281 auto primary_id = owner->consensus->primary();
282 if (!primary_id.has_value())
283 {
285 "BackupSnapshotFetch: No known primary, skipping fetch");
286 return;
287 }
288
289 auto tx = owner->network.tables->create_read_only_tx();
290 auto* nodes = tx.ro<ccf::Nodes>(Tables::NODES);
291 auto node_info = nodes->get(primary_id.value());
292 if (!node_info.has_value())
293 {
295 "BackupSnapshotFetch: Could not find primary node {} in nodes "
296 "table",
297 primary_id.value());
298 return;
299 }
300
301 // Use the configured RPC interface to find the primary's address
302 const auto& target_interface =
303 snapshot_config.backup_fetch.target_rpc_interface;
304 auto iface_it = node_info->rpc_interfaces.find(target_interface);
305 if (iface_it == node_info->rpc_interfaces.end())
306 {
308 "BackupSnapshotFetch: Primary node {} does not have RPC "
309 "interface '{}' configured",
310 primary_id.value(),
311 target_interface);
312 return;
313 }
314 primary_address = iface_it->second.published_address;
315
316 if (owner->network.identity == nullptr)
317 {
319 "BackupSnapshotFetch: No service identity available, cannot "
320 "construct TLS credentials for fetching snapshot");
321 return;
322 }
323
324 service_cert = owner->network.identity->cert.raw();
325 }
326
328 "BackupSnapshotFetch: Attempting to fetch snapshot from primary at "
329 "{}",
330 primary_address);
331
332 const auto& bf = snapshot_config.backup_fetch;
333
334 auto latest_peer_snapshot = snapshots::fetch_from_peer(
335 primary_address,
336 service_cert,
337 bf.max_attempts,
338 bf.retry_interval.count_ms(),
339 bf.max_size.count_bytes(),
340 since_seqno);
341
342 if (latest_peer_snapshot.has_value())
343 {
345 "BackupSnapshotFetch: Received snapshot {} from primary (size: "
346 "{})",
347 latest_peer_snapshot->snapshot_name,
348 latest_peer_snapshot->snapshot_data.size());
349
350 const auto snapshot_path =
351 std::filesystem::path(latest_peer_snapshot->snapshot_name);
352
353 if (
354 snapshot_path.empty() || snapshot_path.is_absolute() ||
355 snapshot_path.has_parent_path() ||
356 snapshot_path.filename() != snapshot_path)
357 {
359 "BackupSnapshotFetch: Rejecting snapshot with invalid name "
360 "'{}' from primary",
361 latest_peer_snapshot->snapshot_name);
362 return;
363 }
364
365 const auto dst_path =
366 std::filesystem::path(snapshot_config.directory) / snapshot_path;
367
368 if (files::exists(dst_path))
369 {
371 "BackupSnapshotFetch: Snapshot {} already exists locally, "
372 "skipping write",
373 dst_path.string());
374 return;
375 }
376
377 files::dump(latest_peer_snapshot->snapshot_data, dst_path);
379 "BackupSnapshotFetch: Wrote snapshot {} ({} bytes)",
380 dst_path.string(),
381 latest_peer_snapshot->snapshot_data.size());
382 }
383 else
384 {
386 "BackupSnapshotFetch: No snapshot available from primary");
387 }
388 }
389
390 [[nodiscard]] const std::string& get_name() const override
391 {
392 static const std::string name = "BackupSnapshotFetch";
393 return name;
394 }
395 };
396
397 private:
398 //
399 // this node's core state
400 //
402 pal::Mutex lock;
403 StartType start_type = StartType::Start;
404
405 ccf::crypto::CurveID curve_id;
406 std::vector<ccf::crypto::SubjectAltName> subject_alt_names;
407
408 std::shared_ptr<ccf::crypto::ECKeyPair_OpenSSL> node_sign_kp;
409 NodeId self;
410 std::shared_ptr<ccf::crypto::RSAKeyPair> node_encrypt_kp;
411 ccf::crypto::Pem self_signed_node_cert;
412 std::optional<ccf::crypto::Pem> endorsed_node_cert = std::nullopt;
413 QuoteInfo quote_info;
414 pal::PlatformAttestationMeasurement node_measurement;
415 std::optional<pal::snp::TcbVersionRaw> snp_tcb_version = std::nullopt;
416 ccf::StartupConfig config;
417 std::optional<pal::UVMEndorsements> snp_uvm_endorsements = std::nullopt;
418 std::shared_ptr<QuoteEndorsementsClient> quote_endorsements_client =
419 nullptr;
420
421 std::atomic<bool> stop_noticed = false;
422
423 //
424 // kv store, replication, and I/O
425 //
426 ringbuffer::AbstractWriterFactory& writer_factory;
427 ringbuffer::WriterPtr to_host;
428 ccf::consensus::Configuration consensus_config;
429 size_t sig_tx_interval = 0;
430 size_t sig_ms_interval = 0;
431
432 NetworkState& network;
433
434 std::shared_ptr<ccf::kv::Consensus> consensus;
435 std::shared_ptr<RPCMap> rpc_map;
436 std::shared_ptr<indexing::Indexer> indexer;
437 std::shared_ptr<NodeToNode> n2n_channels;
438 std::shared_ptr<Forwarder<NodeToNode>> cmd_forwarder;
439 std::shared_ptr<ccf::CommitCallbackSubsystem> commit_callbacks = nullptr;
440 std::shared_ptr<ccf::SignatureCacheSubsystem> signature_cache = nullptr;
441 std::shared_ptr<RPCSessions> rpcsessions;
442
443 std::shared_ptr<ccf::kv::TxHistory> history;
444 std::shared_ptr<ccf::kv::AbstractTxEncryptor> encryptor;
445
446 ShareManager share_manager;
447 std::shared_ptr<Snapshotter> snapshotter;
448
449 //
450 // recovery
451 //
452 std::shared_ptr<ccf::kv::Store> recovery_store;
453
454 ccf::kv::Version recovery_v = 0;
455 ccf::crypto::Sha256Hash recovery_root;
456 std::vector<ccf::kv::Version> view_history;
457 ::consensus::Index last_recovered_signed_idx = 0;
458 RecoveredEncryptedLedgerSecrets recovered_encrypted_ledger_secrets;
459 std::optional<
460 std::tuple<ccf::NodeId, std::vector<uint8_t>, SealedRecoveryKey>>
461 cached_sealed_recovery_data = std::nullopt;
462 ::consensus::Index last_recovered_idx = 0;
463 static const size_t recovery_batch_size = 100;
464
465 //
466 // JWT key auto-refresh
467 //
468 std::shared_ptr<JwtKeyAutoRefresh> jwt_key_auto_refresh;
469
470 std::unique_ptr<StartupSnapshotInfo> startup_snapshot_info = nullptr;
471 // Set to the snapshot seqno when a node starts from one and remembered for
472 // the lifetime of the node
473 ccf::kv::Version startup_seqno = 0;
474
475 ccf::tasks::Task join_periodic_task;
476 ccf::tasks::Task snapshot_fetch_task;
477 ccf::tasks::Task backup_snapshot_fetch_task;
478
479 // Number of times we have fetched the latest snapshot from the primary
480 size_t join_fetch_count = 0;
481
482 std::shared_ptr<ccf::kv::AbstractTxEncryptor> make_encryptor()
483 {
484#ifdef USE_NULL_ENCRYPTOR
485 return std::make_shared<ccf::kv::NullTxEncryptor>();
486#else
487 return std::make_shared<NodeEncryptor>(network.ledger_secrets);
488#endif
489 }
490
491 void find_local_startup_snapshot()
492 {
493 if (start_type != StartType::Join && start_type != StartType::Recover)
494 {
495 return;
496 }
497
498 std::vector<std::filesystem::path> directories;
499 directories.emplace_back(config.snapshots.directory);
500 const auto& read_only_dir = config.snapshots.read_only_directory;
501 if (read_only_dir.has_value())
502 {
503 directories.emplace_back(read_only_dir.value());
504 }
505
506 const auto committed_snapshots =
508 for (const auto& [snapshot_seqno, snapshot_path] : committed_snapshots)
509 {
510 auto snapshot_data = files::slurp(snapshot_path);
511
513 "Found latest local snapshot file: {} (size: {})",
514 snapshot_path,
515 snapshot_data.size());
516
517 const auto segments = separate_segments(snapshot_data);
518
519 try
520 {
521 verify_snapshot(segments, config.recover.previous_service_identity);
522 }
523 catch (const std::exception& e)
524 {
526 "Error while verifying {}: {}", snapshot_path.string(), e.what());
527
528 const auto dir = snapshot_path.parent_path();
529 const auto file_name = snapshot_path.filename();
530
531 if (dir == config.snapshots.directory)
532 {
534 "Ignoring corrupt snapshot {} in directory {} and looking for "
535 "next",
536 dir.string(),
537 snapshot_path.string());
538 try
539 {
540 snapshots::ignore_snapshot_file(dir, file_name.string());
541 }
542 catch (std::logic_error& e)
543 {
544 LOG_FAIL_FMT("Unable to mark snapshot as ignored: {}", e.what());
545 }
546 }
547 else
548 {
550 "Snapshot {} is in a read-only directory {}, so will not be "
551 "modified. Ignoring and looking for next",
552 snapshot_path.string(),
553 dir.string());
554 }
555
556 continue;
557 }
558
559 set_startup_snapshot(snapshot_seqno, std::move(snapshot_data));
560 return;
561 }
562
563 LOG_INFO_FMT("No local snapshot found");
564 }
565
566 void set_startup_snapshot(
567 ccf::kv::Version snapshot_seqno, std::vector<uint8_t>&& snapshot_data)
568 {
569 startup_snapshot_info = std::make_unique<StartupSnapshotInfo>(
570 snapshot_seqno, std::move(snapshot_data));
571
572 startup_seqno = startup_snapshot_info->seqno;
573 last_recovered_idx = startup_seqno;
574 last_recovered_signed_idx = last_recovered_idx;
575
576 if (start_type == StartType::Join)
577 {
578 // after fetching a snapshot, subsequent requests should use the
579 // required bound instead of the preferred bound
580 join_fetch_count += 1;
581 }
582
583 if (start_type == StartType::Recover)
584 {
585 const auto segments = separate_segments(startup_snapshot_info->raw);
586
588 deserialise_snapshot(
589 network.tables,
590 segments,
591 hooks,
592 &view_history,
593 true /* public_only */);
594
595 {
596 auto tx = network.tables->create_read_only_tx();
597 auto status =
598 tx.ro<SnapshotStatusValue>(Tables::SNAPSHOT_STATUS)->get();
599 if (status.has_value())
600 {
601 snapshotter->init_from_snapshot_status(status.value());
602 }
603 }
604 }
605 }
606
607 RecoveryDecisionProtocolSubsystem recovery_decision_protocol;
608
609 public:
611 ringbuffer::AbstractWriterFactory& writer_factory,
612 NetworkState& network,
613 std::shared_ptr<RPCSessions> rpcsessions,
614 ccf::crypto::CurveID curve_id_) :
615 sm("NodeState", NodeStartupState::uninitialized),
616 curve_id(curve_id_),
617 node_sign_kp(std::make_shared<ccf::crypto::ECKeyPair_OpenSSL>(curve_id_)),
618 self(compute_node_id_from_kp(node_sign_kp)),
619 node_encrypt_kp(ccf::crypto::make_rsa_key_pair()),
620 writer_factory(writer_factory),
621 to_host(writer_factory.create_writer_to_outside()),
622 network(network),
623 rpcsessions(std::move(rpcsessions)),
624 share_manager(network.ledger_secrets),
625 recovery_decision_protocol(this)
626 {}
627
630 const QuoteInfo& quote_info_,
631 const std::vector<uint8_t>& expected_node_public_key_der,
633 const std::optional<std::vector<uint8_t>>& code_transparent_statement,
634 std::shared_ptr<NetworkIdentitySubsystemInterface>
635 network_identity_subsystem) override
636 {
638 tx,
639 quote_info_,
640 expected_node_public_key_der,
641 measurement,
642 code_transparent_statement,
643 network_identity_subsystem);
644 }
645
646 //
647 // funcs in state "uninitialized"
648 //
650 const ccf::consensus::Configuration& consensus_config_,
651 std::shared_ptr<RPCMap> rpc_map_,
652 std::shared_ptr<AbstractRPCResponder> rpc_sessions_,
653 std::shared_ptr<indexing::Indexer> indexer_,
654 std::shared_ptr<ccf::CommitCallbackSubsystem> commit_callbacks_,
655 std::shared_ptr<ccf::SignatureCacheSubsystem> signature_cache_,
656 size_t sig_tx_interval_,
657 size_t sig_ms_interval_)
658 {
659 std::lock_guard<pal::Mutex> guard(lock);
661
662 consensus_config = consensus_config_;
663 rpc_map = rpc_map_;
664
665 indexer = indexer_;
666 commit_callbacks = commit_callbacks_;
667 signature_cache = signature_cache_;
668
669 sig_tx_interval = sig_tx_interval_;
670 sig_ms_interval = sig_ms_interval_;
671
672 n2n_channels = std::make_shared<NodeToNodeChannelManager>(writer_factory);
673
674 cmd_forwarder = std::make_shared<Forwarder<NodeToNode>>(
675 rpc_sessions_, n2n_channels, rpc_map);
676
678
679 for (auto& [actor, fe] : rpc_map->frontends())
680 {
681 fe->set_sig_intervals(sig_tx_interval, sig_ms_interval);
682 fe->set_cmd_forwarder(cmd_forwarder);
683 }
684 }
685
686 //
687 // funcs in state "initialized"
688 //
690 {
691 auto measurement = AttestationProvider::get_measurement(quote_info);
692 if (measurement.has_value())
693 {
694 node_measurement = measurement.value();
695 }
696 else
697 {
698 throw std::logic_error("Failed to extract code id from quote");
699 }
700
701 auto snp_attestation =
703 if (snp_attestation.has_value())
704 {
705 snp_tcb_version = snp_attestation.value().reported_tcb;
706 }
707
708 // Verify that the security policy matches the quoted digest of the policy
709 if (!config.attestation.environment.security_policy.has_value())
710 {
712 "Security policy not set, skipping check against attestation host "
713 "data");
714 }
715 else
716 {
717 auto quoted_digest = AttestationProvider::get_host_data(quote_info);
718 if (!quoted_digest.has_value())
719 {
720 throw std::logic_error("Unable to find host data in attestation");
721 }
722
723 auto const& security_policy =
725
726 auto security_policy_digest =
727 quote_info.format == QuoteFormat::amd_sev_snp_v1 ?
729 ccf::crypto::Sha256Hash(security_policy);
730 if (security_policy_digest != quoted_digest.value())
731 {
732 throw std::logic_error(fmt::format(
733 "Digest of decoded security policy \"{}\" {} does not match "
734 "attestation host data {}",
735 security_policy,
736 security_policy_digest.hex_str(),
737 quoted_digest.value().hex_str()));
738 }
740 "Successfully verified attested security policy {}",
741 security_policy_digest);
742 }
743
744 if (quote_info.format == QuoteFormat::amd_sev_snp_v1)
745 {
746 if (!config.attestation.environment.uvm_endorsements.has_value())
747 {
749 "UVM endorsements not set, skipping check against attestation "
750 "measurement");
751 }
752 else
753 {
754 try
755 {
756 auto uvm_endorsements_raw = ccf::crypto::raw_from_b64(
758 // A node at this stage does not have a notion of what UVM
759 // descriptor is acceptable. That is decided either by the Joinee,
760 // or by Consortium endorsing the Start or Recovery node. For that
761 // reason, we extract an endorsement descriptor from the UVM
762 // endorsements and make it available in the ledger's initial or
763 // recovery transaction.
764 snp_uvm_endorsements = pal::verify_uvm_endorsements_descriptor(
765 uvm_endorsements_raw, node_measurement);
766 quote_info.uvm_endorsements = uvm_endorsements_raw;
768 "Successfully verified attested UVM endorsements: {}",
769 snp_uvm_endorsements->to_str());
770 }
771 catch (const std::exception& e)
772 {
773 throw std::logic_error(
774 fmt::format("Error verifying UVM endorsements: {}", e.what()));
775 }
776 }
777 }
778
779 switch (start_type)
780 {
781 case StartType::Start:
782 {
783 create_and_send_boot_request(
784 aft::starting_view_change, true /* Create new consortium */);
785 return;
786 }
787 case StartType::Join:
788 {
789 find_local_startup_snapshot();
790
793 return;
794 }
796 {
797 setup_recovery_hook();
798
799 find_local_startup_snapshot();
800
803 return;
804 }
805 default:
806 {
807 throw std::logic_error(
808 fmt::format("Node was launched in unknown mode {}", start_type));
809 }
810 }
811 }
812
814 {
815 auto fetch_endorsements = [this](
816 const QuoteInfo& qi,
817 const pal::snp::
818 EndorsementEndpointsConfiguration&
819 endpoint_config) {
820 // Note: Node lock is already taken here as this is called back
821 // synchronously with the call to pal::generate_quote
822 this->quote_info = qi;
823
824 auto b64encoded_quote = ccf::crypto::b64url_from_raw(quote_info.quote);
825 nlohmann::json jq;
826 to_json(jq, quote_info.format);
828 "Initial node attestation ({}): {}", jq.dump(), b64encoded_quote);
829
830 if (quote_info.format == QuoteFormat::amd_sev_snp_v1)
831 {
832 // Use endorsements retrieved from file, if available
833 if (config.attestation.environment.snp_endorsements.has_value())
834 {
835 try
836 {
837 const auto raw_data = ccf::crypto::raw_from_b64(
839
840 const auto j = nlohmann::json::parse(raw_data);
841 const auto aci_endorsements =
843
844 // Check that tcbm in endorsement matches reported TCB in our
845 // retrieved attestation
846 const auto* quote =
847 reinterpret_cast<const ccf::pal::snp::Attestation*>(
848 quote_info.quote.data());
849 const auto reported_tcb = quote->reported_tcb;
850
851 // tcbm is a single hex value, like DB18000000000004. To match
852 // that with a TcbVersion, reverse the bytes.
853 const auto* tcb_begin =
854 reinterpret_cast<const uint8_t*>(&reported_tcb);
855 const std::span<const uint8_t> tcb_bytes{
856 tcb_begin, tcb_begin + sizeof(reported_tcb)};
857 auto tcb_as_hex = fmt::format(
858 "{:02x}", fmt::join(tcb_bytes.rbegin(), tcb_bytes.rend(), ""));
859 ccf::nonstd::to_upper(tcb_as_hex);
860
861 if (tcb_as_hex == aci_endorsements.tcbm)
862 {
864 "Using SNP endorsements loaded from file, endorsing TCB {}",
865 tcb_as_hex);
866
867 auto& endorsements_pem = quote_info.endorsements;
868 endorsements_pem.insert(
869 endorsements_pem.end(),
870 aci_endorsements.vcek_cert.begin(),
871 aci_endorsements.vcek_cert.end());
872 endorsements_pem.insert(
873 endorsements_pem.end(),
874 aci_endorsements.certificate_chain.begin(),
875 aci_endorsements.certificate_chain.end());
876
877 try
878 {
879 launch_node();
880 return;
881 }
882 catch (const std::exception& e)
883 {
884 LOG_FAIL_FMT("Failed to launch node: {}", e.what());
885 throw;
886 }
887 }
888 else
889 {
891 "SNP endorsements loaded from disk ({}) contained tcbm {}, "
892 "which does not match reported TCB of current attestation "
893 "{}. "
894 "Falling back to fetching fresh endorsements from server.",
895 config.attestation.snp_endorsements_file.value(),
896 aci_endorsements.tcbm,
897 tcb_as_hex);
898 }
899 }
900 catch (const std::exception& e)
901 {
903 "Error attempting to use SNP endorsements from file: {}",
904 e.what());
905 }
906 }
907
908 if (config.attestation.snp_endorsements_servers.empty())
909 {
910 throw std::runtime_error(
911 "One or more SNP endorsements servers must be specified to fetch "
912 "the collateral for the attestation");
913 }
914 // On SEV-SNP, fetch endorsements from servers if specified
915 quote_endorsements_client = std::make_shared<QuoteEndorsementsClient>(
916 endpoint_config, [this](std::vector<uint8_t>&& endorsements) {
917 std::lock_guard<pal::Mutex> guard(lock);
918 quote_info.endorsements = std::move(endorsements);
919 try
920 {
921 launch_node();
922 }
923 catch (const std::exception& e)
924 {
925 LOG_FAIL_FMT("{}", e.what());
926 throw;
927 }
928 quote_endorsements_client.reset();
929 });
930
931 quote_endorsements_client->fetch_endorsements();
932 return;
933 }
934
935 if (quote_info.format != QuoteFormat::insecure_virtual)
936 {
937 throw std::runtime_error(fmt::format(
938 "Unsupported quote format: {}",
939 static_cast<int>(quote_info.format)));
940 }
941
942 launch_node();
943 };
944
946 ccf::crypto::Sha256Hash((node_sign_kp->public_key_der()));
947
948 pal::generate_quote(
949 report_data,
950 fetch_endorsements,
952 }
953
955 StartType start_type_, const ccf::StartupConfig& config_)
956 {
957 std::lock_guard<pal::Mutex> guard(lock);
959 start_type = start_type_;
960
961 config = config_;
962 subject_alt_names = get_subject_alternative_names();
963
965 self_signed_node_cert = create_self_signed_cert(
966 node_sign_kp,
968 subject_alt_names,
969 config.startup_host_time,
971
972 accept_node_tls_connections();
973 open_frontend(ActorsType::nodes);
974
975 // Signatures are only emitted on a timer once the public ledger has been
976 // recovered
977 setup_history();
978 setup_snapshotter();
979 setup_encryptor();
980
982
983 switch (start_type)
984 {
985 case StartType::Start:
986 {
987 network.identity = std::make_unique<ccf::NetworkIdentity>(
989 curve_id,
990 config.startup_host_time,
992
993 network.ledger_secrets->init();
994
995 history->set_service_signing_identity(
996 network.identity->get_key_pair(), config.cose_signatures);
997
998 setup_consensus(false, endorsed_node_cert);
999
1000 // Become the primary and force replication
1001 consensus->force_become_primary();
1002
1003 LOG_INFO_FMT("Created new node {}", self);
1004 return {self_signed_node_cert, network.identity->cert};
1005 }
1006 case StartType::Join:
1007 {
1008 LOG_INFO_FMT("Created join node {}", self);
1009 return {self_signed_node_cert, {}};
1010 }
1011 case StartType::Recover:
1012 {
1014 {
1015 throw std::logic_error(
1016 "Recovery requires the certificate of the previous service "
1017 "identity");
1018 }
1019
1020 ccf::crypto::Pem previous_service_identity_cert(
1021 config.recover.previous_service_identity.value());
1022
1023 network.identity = std::make_unique<ccf::NetworkIdentity>(
1024 ccf::crypto::get_subject_name(previous_service_identity_cert),
1025 curve_id,
1026 config.startup_host_time,
1028
1029 LOG_INFO_FMT("Created recovery node {}", self);
1030 return {self_signed_node_cert, network.identity->cert};
1031 }
1032 default:
1033 {
1034 throw std::logic_error(
1035 fmt::format("Node was started in unknown mode {}", start_type));
1036 }
1037 }
1038 }
1039
1040 //
1041 // funcs in state "pending"
1042 //
1043
1045 {
1047
1048 auto network_ca = std::make_shared<::tls::CA>(std::string(
1049 config.join.service_cert.begin(), config.join.service_cert.end()));
1050
1051 auto [target_host, target_port] =
1052 split_net_address(config.join.target_rpc_address);
1053
1054 auto join_client_cert = std::make_unique<::tls::Cert>(
1055 network_ca,
1056 self_signed_node_cert,
1057 node_sign_kp->private_key_pem(),
1058 target_host);
1059
1060 // Create RPC client and connect to remote node
1061 // Note: For now, assume that target node accepts same application
1062 // protocol as this node's main RPC interface
1063 auto join_client = rpcsessions->create_client(
1064 std::move(join_client_cert),
1065 rpcsessions->get_app_protocol_main_interface());
1066
1067 join_client->connect(
1068 target_host,
1069 target_port,
1070 // Capture target_address by value, and use them when
1071 // logging about this response. Do not use config target address, which
1072 // may have updated in the interim.
1073 [this, target_address = config.join.target_rpc_address](
1074 ccf::http_status status,
1075 http::HeaderMap&& headers,
1076 std::vector<uint8_t>&& data) {
1077 std::lock_guard<pal::Mutex> guard(lock);
1078 if (!sm.check(NodeStartupState::pending))
1079 {
1080 return;
1081 }
1082
1083 if (is_http_status_client_error(status))
1084 {
1085 std::optional<ccf::ODataErrorResponse> error_response =
1086 std::nullopt;
1087
1088 try
1089 {
1090 auto j = nlohmann::json::parse(data);
1091 error_response = j.get<ccf::ODataErrorResponse>();
1092 }
1093 catch (const nlohmann::json::exception& e)
1094 {
1095 // Leave error_response == nullopt
1097 "Join request returned {}, body is not ODataErrorResponse: {}",
1098 status,
1099 std::string(data.begin(), data.end()));
1100 }
1101
1102 if (
1103 error_response.has_value() &&
1104 error_response->error.code == ccf::errors::StartupSeqnoIsOld &&
1106 {
1108 "Join request to {} returned {} error. Attempting to fetch "
1109 "fresher snapshot",
1110 target_address,
1111 ccf::errors::StartupSeqnoIsOld);
1112
1113 // If we've followed a redirect, it will have been updated in
1114 // config.join. Note that this is fire-and-forget, it is assumed
1115 // that it proceeds in the background, updating state when it
1116 // completes, and the join timer separately re-attempts join after
1117 // this succeeds
1118 if (
1119 snapshot_fetch_task != nullptr &&
1120 !snapshot_fetch_task->is_cancelled())
1121 {
1122 LOG_INFO_FMT("Snapshot fetch already in progress, skipping");
1123 }
1124 else
1125 {
1126 snapshot_fetch_task = std::make_shared<FetchSnapshot>(
1127 config.join, config.snapshots, this);
1128 ccf::tasks::add_task(snapshot_fetch_task);
1129 }
1130 return;
1131 }
1132
1133 auto error_msg = fmt::format(
1134 "Join request to {} returned {} Bad Request: {}. Shutting "
1135 "down node gracefully.",
1136 target_address,
1137 status,
1138 std::string(data.begin(), data.end()));
1139 LOG_FAIL_FMT("{}", error_msg);
1141 AdminMessage::fatal_error_msg, to_host, error_msg);
1142 return;
1143 }
1144
1145 if (status != HTTP_STATUS_OK)
1146 {
1147 const auto& location = headers.find(http::headers::LOCATION);
1148 if (
1149 config.join.follow_redirect &&
1150 (status == HTTP_STATUS_PERMANENT_REDIRECT ||
1151 status == HTTP_STATUS_TEMPORARY_REDIRECT) &&
1152 location != headers.end())
1153 {
1154 const auto& url = ::http::parse_url_full(location->second);
1155 config.join.target_rpc_address =
1156 make_net_address(url.host, url.port);
1157 LOG_INFO_FMT("Target node redirected to {}", location->second);
1158 }
1159 else
1160 {
1162 "An error occurred while joining the network: {} {}{}",
1163 status,
1164 ccf::http_status_str(status),
1165 data.empty() ?
1166 "" :
1167 fmt::format(" '{}'", std::string(data.begin(), data.end())));
1168 }
1169 return;
1170 }
1171
1173 try
1174 {
1175 auto j = nlohmann::json::parse(data);
1176 resp = j.get<JoinNetworkNodeToNode::Out>();
1177 }
1178 catch (const std::exception& e)
1179 {
1181 "An error occurred while parsing the join network response");
1182
1183 LOG_DEBUG_FMT("Join network response error: {}", e.what());
1185 "Join network response body: {}",
1186 std::string(data.begin(), data.end()));
1187
1188 return;
1189 }
1190
1191 // Set network secrets, node id and become part of network.
1192 if (resp.node_status == NodeStatus::TRUSTED)
1193 {
1194 if (!resp.network_info.has_value())
1195 {
1196 throw std::logic_error("Expected network info in join response");
1197 }
1198
1199 network.identity = std::make_unique<ccf::NetworkIdentity>(
1200 resp.network_info->identity);
1201 network.ledger_secrets->init_from_map(
1202 std::move(resp.network_info->ledger_secrets));
1203
1204 history->set_service_signing_identity(
1205 network.identity->get_key_pair(),
1206 resp.network_info->cose_signatures_config.value_or(
1208
1209 ccf::crypto::Pem n2n_channels_cert;
1210 if (!resp.network_info->endorsed_certificate.has_value())
1211 {
1212 // Endorsed certificate was added to join response in 2.x
1213 throw std::logic_error(
1214 "Expected endorsed certificate in join response");
1215 }
1216 n2n_channels_cert = resp.network_info->endorsed_certificate.value();
1217
1218 setup_consensus(resp.network_info->public_only, n2n_channels_cert);
1220
1221 if (resp.network_info->public_only)
1222 {
1223 last_recovered_signed_idx =
1224 resp.network_info->last_recovered_signed_idx;
1225 setup_recovery_hook();
1226 snapshotter->set_snapshot_generation(false);
1227 }
1228
1229 View view = VIEW_UNKNOWN;
1230 std::vector<ccf::kv::Version> view_history_ = {};
1231 if (startup_snapshot_info)
1232 {
1233 // It is only possible to deserialise the entire snapshot now,
1234 // once the ledger secrets have been passed in by the network
1236 deserialise_snapshot(
1237 network.tables,
1238 startup_snapshot_info->raw,
1239 hooks,
1240 &view_history_,
1241 resp.network_info->public_only);
1242
1243 for (auto& hook : hooks)
1244 {
1245 hook->call(consensus.get());
1246 }
1247
1248 auto tx = network.tables->create_read_only_tx();
1249 view = resolve_latest_sig_view(tx);
1250
1251 if (!resp.network_info->public_only)
1252 {
1253 // Only clear snapshot if not recovering. When joining the
1254 // public network the snapshot is used later to initialise the
1255 // recovery store
1256 startup_snapshot_info.reset();
1257 }
1258
1260 "Joiner successfully resumed from snapshot at seqno {} and "
1261 "view {}",
1262 network.tables->current_version(),
1263 view);
1264 }
1265
1266 consensus->init_as_backup(
1267 network.tables->current_version(),
1268 view,
1269 view_history_,
1270 last_recovered_signed_idx);
1271
1272 {
1273 auto snap_tx = network.tables->create_read_only_tx();
1274 auto snapshot_status =
1275 snap_tx.ro<SnapshotStatusValue>(Tables::SNAPSHOT_STATUS)->get();
1276 if (snapshot_status.has_value())
1277 {
1278 snapshotter->init_from_snapshot_status(snapshot_status.value());
1279 }
1280 }
1281 history->start_signature_emit_timer();
1282
1283 if (resp.network_info->public_only)
1284 {
1286 }
1287 else
1288 {
1289 reset_data(quote_info.quote);
1290 reset_data(quote_info.endorsements);
1292 }
1293
1294 if (join_periodic_task != nullptr)
1295 {
1296 join_periodic_task->cancel_task();
1297 join_periodic_task = nullptr;
1298 }
1299
1301 "Node has now joined the network as node {}: {}",
1302 self,
1303 (resp.network_info->public_only ? "public only" : "all domains"));
1304 }
1305 else if (resp.node_status == NodeStatus::PENDING)
1306 {
1308 "Node {} is waiting for votes of members to be trusted", self);
1309 }
1310 },
1311 [this](const std::string& error_msg) {
1312 std::lock_guard<pal::Mutex> guard(lock);
1313 auto long_error_msg = fmt::format(
1314 "Early error when joining existing network at {}: {}. Shutting "
1315 "down node gracefully...",
1316 config.join.target_rpc_address,
1317 error_msg);
1318 LOG_FAIL_FMT("{}", long_error_msg);
1320 AdminMessage::fatal_error_msg, to_host, long_error_msg);
1321 });
1322
1323 // Send RPC request to remote node to join the network.
1324 JoinNetworkNodeToNode::In join_params;
1325
1326 join_params.node_info_network = config.network;
1327 join_params.public_encryption_key = node_encrypt_kp->public_key_pem();
1328 join_params.quote_info = quote_info;
1329 join_params.startup_seqno = startup_seqno;
1330 if (config.join.fetch_recent_snapshot)
1331 {
1332 join_params.join_fetch_count = join_fetch_count;
1333 }
1334 else
1335 {
1336 join_params.join_fetch_count = 1;
1337 }
1338 join_params.certificate_signing_request = node_sign_kp->create_csr(
1339 config.node_certificate.subject_name, subject_alt_names);
1340 join_params.node_data = config.node_data;
1341 join_params.ledger_sign_mode = ccf::get_ledger_sign_mode();
1342 if (config.sealing_recovery.has_value() && snp_tcb_version.has_value())
1343 {
1344 join_params.sealing_recovery_data = std::make_pair(
1345 sealing::get_snp_sealed_recovery_key(snp_tcb_version.value()),
1346 config.sealing_recovery->location.name);
1347 }
1348
1349 if (config.join.host_data_transparent_statement_path.has_value())
1350 {
1352 "Reading code_transparent_statement from file: {}",
1354 auto ts = files::slurp(
1356 join_params.code_transparent_statement = std::move(ts);
1357 }
1358
1360 "Sending join request to {}", config.join.target_rpc_address);
1361
1362 const auto body = nlohmann::json(join_params).dump();
1363
1364 LOG_DEBUG_FMT("Sending join request body: {}", body);
1365
1367 fmt::format("/{}/{}", get_actor_prefix(ActorsType::nodes), "join"));
1368 r.set_header(
1369 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1370 r.set_body(body);
1371
1372 join_client->send_request(std::move(r));
1373 }
1374
1376 {
1377 std::lock_guard<pal::Mutex> guard(lock);
1378 initiate_join_unsafe();
1379 }
1380
1382 {
1383 initiate_join_unsafe();
1384
1385 join_periodic_task = ccf::tasks::make_basic_task([this]() {
1386 std::lock_guard<pal::Mutex> guard(this->lock);
1387 if (this->sm.check(NodeStartupState::pending))
1388 {
1389 this->initiate_join_unsafe();
1390 }
1391 });
1392
1394 join_periodic_task,
1395 config.join.retry_timeout,
1396 config.join.retry_timeout);
1397 }
1398
1400 {
1401 if (!consensus)
1402 {
1404 "JWT key auto-refresh: consensus not initialized, not starting "
1405 "auto-refresh");
1406 return;
1407 }
1408 jwt_key_auto_refresh = std::make_shared<JwtKeyAutoRefresh>(
1410 network,
1411 consensus,
1412 rpcsessions,
1413 rpc_map,
1414 node_sign_kp,
1415 self_signed_node_cert);
1416 jwt_key_auto_refresh->start();
1417
1418 network.tables->set_map_hook(
1419 network.jwt_issuers.get_name(),
1422 jwt_key_auto_refresh->schedule_once();
1423 return {nullptr};
1424 });
1425 }
1426
1427 size_t get_jwt_attempts() override
1428 {
1429 return jwt_key_auto_refresh->get_attempts();
1430 }
1431
1432 //
1433 // funcs in state "readingPublicLedger"
1434 //
1436 {
1437 if (!sm.check(NodeStartupState::readingPublicLedger))
1438 {
1439 throw std::logic_error(fmt::format(
1440 "Node should be in state {} to start reading ledger",
1441 NodeStartupState::readingPublicLedger));
1442 }
1443
1444 LOG_INFO_FMT("Starting to read public ledger");
1445
1446 read_ledger_entries(
1447 last_recovered_idx + 1, last_recovered_idx + recovery_batch_size);
1448 }
1449
1450 void recover_public_ledger_entries(const std::vector<uint8_t>& entries)
1451 {
1452 std::lock_guard<pal::Mutex> guard(lock);
1453
1454 sm.expect(NodeStartupState::readingPublicLedger);
1455
1456 const auto* data = entries.data();
1457 auto size = entries.size();
1458
1459 if (size == 0)
1460 {
1461 recover_public_ledger_end_unsafe();
1462 return;
1463 }
1464
1465 while (size > 0)
1466 {
1467 auto entry = ::consensus::LedgerEnclave::get_entry(data, size);
1468
1470 "Deserialising public ledger entry #{} [{} bytes]",
1471 last_recovered_idx,
1472 entry.size());
1473
1474 // When reading the private ledger, deserialise in the recovery store
1475
1477 try
1478 {
1479 auto r = network.tables->deserialize(entry, true);
1480 result = r->apply();
1481 if (result == ccf::kv::ApplyResult::FAIL)
1482 {
1484 "Failed to deserialise public ledger entry: {}", result);
1485 recover_public_ledger_end_unsafe();
1486 return;
1487 }
1488 ++last_recovered_idx;
1489
1490 // Not synchronised because consensus isn't effectively running then
1491 for (auto& hook : r->get_hooks())
1492 {
1493 hook->call(consensus.get());
1494 }
1495 }
1496 catch (const std::exception& e)
1497 {
1499 "Failed to deserialise public ledger entry: {}", e.what());
1500 recover_public_ledger_end_unsafe();
1501 return;
1502 }
1503
1504 // If the ledger entry is a signature, it is safe to compact the store
1506 {
1507 // If the ledger entry is a signature, it is safe to compact the store
1508 network.tables->compact(last_recovered_idx);
1509 auto tx = network.tables->create_read_only_tx();
1510
1512
1514 "Read signature at {} for view {}", last_recovered_idx, sig_view);
1515 // Initial transactions, before the first signature, must have
1516 // happened in the first signature's view (eg - if the first
1517 // signature is at seqno 20 in view 4, then transactions 1->19 must
1518 // also have been in view 4). The brief justification is that while
1519 // the first node may start in an arbitrarily high view (it does not
1520 // necessarily start in view 1), it cannot _change_ view before a
1521 // valid signature.
1522 const auto view_start_idx =
1523 view_history.empty() ? 1 : last_recovered_signed_idx + 1;
1524 CCF_ASSERT_FMT(sig_view >= 0, "sig_view is invalid, {}", sig_view);
1525 for (auto i = view_history.size(); i < static_cast<size_t>(sig_view);
1526 ++i)
1527 {
1528 view_history.push_back(view_start_idx);
1529 }
1530 last_recovered_signed_idx = last_recovered_idx;
1531 }
1532 }
1533
1534 read_ledger_entries(
1535 last_recovered_idx + 1, last_recovered_idx + recovery_batch_size);
1536 }
1537
1539 {
1540 std::lock_guard<pal::Mutex> guard(lock);
1541 sm.expect(NodeStartupState::readingPublicLedger);
1542 history->start_signature_emit_timer();
1543 sm.advance(NodeStartupState::partOfPublicNetwork);
1544 }
1545
1547 {
1548 std::lock_guard<pal::Mutex> guard(lock);
1549 sm.expect(NodeStartupState::initialized);
1550 history->start_signature_emit_timer();
1551 auto_refresh_jwt_keys();
1552 reset_data(quote_info.quote);
1553 reset_data(quote_info.endorsements);
1554 sm.advance(NodeStartupState::partOfNetwork);
1555 }
1556
1558 {
1559 sm.expect(NodeStartupState::readingPublicLedger);
1560
1561 // When reaching the end of the public ledger, truncate to last signed
1562 // index
1563 const auto last_recovered_term = view_history.size();
1564 auto new_term = last_recovered_term + aft::starting_view_change;
1565 LOG_INFO_FMT("Setting term on public recovery store to {}", new_term);
1566
1567 // Note: KV term must be set before the first Tx is committed
1568 network.tables->rollback(
1569 {last_recovered_term, last_recovered_signed_idx}, new_term);
1570 ledger_truncate(last_recovered_signed_idx, true);
1571 snapshotter->rollback(last_recovered_signed_idx);
1572
1574 "End of public ledger recovery - Truncating ledger to last signed "
1575 "TxID: {}.{}",
1576 last_recovered_term,
1577 last_recovered_signed_idx);
1578
1579 auto tx = network.tables->create_read_only_tx();
1580 network.ledger_secrets->init(last_recovered_signed_idx + 1);
1581
1582 // Initialise snapshotter after public recovery
1583 snapshotter->init_after_public_recovery();
1584 snapshotter->set_snapshot_generation(false);
1585
1586 ccf::kv::Version sig_seqno = 0;
1587 ccf::kv::Version cose_seqno = 0;
1588 ccf::kv::Version index = 0;
1589 ccf::kv::Term view = 0;
1590
1591 auto ls = tx.ro(network.signatures)->get();
1592 if (ls.has_value())
1593 {
1594 auto s = ls.value();
1595 sig_seqno = s.seqno;
1596 index = s.seqno;
1597 view = s.view;
1598 }
1599
1601 auto lcs = tx.ro(network.cose_signatures)->get();
1602 if (lcs.has_value())
1603 {
1604 CoseSignature cs = lcs.value();
1605 LOG_INFO_FMT("COSE signature found after recovery");
1606
1607 auto as_receipt =
1608 cose::decode_ccf_receipt(cs, /* recompute_root */ false);
1609
1610 try
1611 {
1612 auto tx_id_opt = ccf::TxID::from_str(as_receipt.phdr.ccf.txid);
1613 if (!tx_id_opt.has_value())
1614 {
1615 throw std::logic_error(fmt::format(
1616 "Failed to parse TxID from COSE signature: {}",
1617 as_receipt.phdr.ccf.txid));
1618 }
1619
1620 cose_seqno = tx_id_opt->seqno;
1621
1622 // Use the COSE signature's TxID only if it is newer than the
1623 // traditional signature's. In flip-flop scenarios (dual -> COSE ->
1624 // dual) the COSE signature may be older than the traditional one.
1625 if (tx_id_opt->seqno > index)
1626 {
1627 index = tx_id_opt->seqno;
1628 view = tx_id_opt->view;
1629 }
1630
1631 auto issuer = as_receipt.phdr.cwt.iss;
1632 auto subject = as_receipt.phdr.cwt.sub;
1634 "COSE signature issuer: {}, subject: {}", issuer, subject);
1635
1636 cs_cfg = ccf::COSESignaturesConfig{issuer, subject};
1637 }
1638 catch (const cose::COSEDecodeError& e)
1639 {
1640 LOG_FAIL_FMT("COSE signature decode error: {}", e.what());
1641 throw;
1642 }
1643 }
1644 else
1645 {
1646 LOG_INFO_FMT("No COSE signature found after recovery");
1647 }
1648
1649 if (!ls.has_value() && !lcs.has_value())
1650 {
1651 throw std::logic_error("No signature found after recovery");
1652 }
1653
1654 // Prevent downgrade: a Dual binary must not recover a COSE-only ledger
1655 // (one where the latest signature is COSE-only, i.e. COSE seqno is
1656 // strictly ahead of any traditional signature).
1657 if (
1659 lcs.has_value() && cose_seqno > sig_seqno)
1660 {
1661 throw std::logic_error(
1662 "Cannot recover a COSE-only ledger with a Dual signing binary. "
1663 "Use a COSE-only binary to recover this ledger.");
1664 }
1665
1666 history->set_service_signing_identity(
1667 network.identity->get_key_pair(), cs_cfg);
1668
1669 auto* h = dynamic_cast<MerkleTxHistory*>(history.get());
1670 if (h != nullptr)
1671 {
1672 h->set_node_id(self);
1673 }
1674
1675 // cache the previous sealed_recovery_key
1676 if (config.sealing_recovery.has_value())
1677 {
1678 auto& name = config.sealing_recovery->location.name;
1679 auto* node_id_lookup =
1680 tx.ro<LocalSealingNodeIdMap>(Tables::SEALING_RECOVERY_NAMES);
1681 auto local_sealing_node_id_opt = node_id_lookup->get(name);
1682 if (local_sealing_node_id_opt.has_value())
1683 {
1684 auto& local_sealing_node_id = local_sealing_node_id_opt.value();
1685 auto sealed_recovery_shares_opt =
1686 tx.ro<SealedShares>(Tables::SEALED_SHARES)->get();
1687 if (sealed_recovery_shares_opt.has_value())
1688 {
1689 auto sealed_recovery_shares = sealed_recovery_shares_opt.value();
1690 auto sealed_share_it =
1691 sealed_recovery_shares.encrypted_wrapping_keys.find(
1692 local_sealing_node_id);
1693
1694 auto sealed_recovery_key =
1695 tx.ro<SealedRecoveryKeys>(Tables::SEALED_RECOVERY_KEYS)
1696 ->get(local_sealing_node_id.value());
1697
1698 if (
1699 sealed_share_it !=
1700 sealed_recovery_shares.encrypted_wrapping_keys.end() &&
1701 sealed_recovery_key.has_value())
1702 {
1703 cached_sealed_recovery_data = std::make_tuple(
1704 local_sealing_node_id.value(),
1705 sealed_share_it->second,
1706 sealed_recovery_key.value());
1707 }
1708 }
1709 }
1710
1711 if (!cached_sealed_recovery_data.has_value())
1712 {
1713 throw std::logic_error(fmt::format(
1714 "Failed to find sealed recovery data for location ({}) in ledger "
1715 "at {}",
1716 name,
1717 last_recovered_signed_idx));
1718 }
1719 }
1720
1721 setup_consensus(true);
1722 auto_refresh_jwt_keys();
1723
1724 LOG_DEBUG_FMT("Restarting consensus at view: {} seqno: {}", view, index);
1725
1726 consensus->force_become_primary(index, view, view_history, index);
1727
1728 create_and_send_boot_request(
1729 new_term, false /* Restore consortium from ledger */);
1730 }
1731
1732 //
1733 // funcs in state "readingPrivateLedger"
1734 //
1735 void recover_private_ledger_entries(const std::vector<uint8_t>& entries)
1736 {
1737 std::lock_guard<pal::Mutex> guard(lock);
1738 if (!sm.check(NodeStartupState::readingPrivateLedger))
1739 {
1741 "Node in state {} cannot recover private ledger entries", sm.value());
1742 return;
1743 }
1744
1745 const auto* data = entries.data();
1746 auto size = entries.size();
1747
1748 if (size == 0)
1749 {
1750 recover_private_ledger_end_unsafe();
1751 return;
1752 }
1753
1754 while (size > 0)
1755 {
1756 auto entry = ::consensus::LedgerEnclave::get_entry(data, size);
1757
1759 "Deserialising private ledger entry {} [{}]",
1760 last_recovered_idx + 1,
1761 entry.size());
1762
1763 // When reading the private ledger, deserialise in the recovery store
1765 try
1766 {
1767 result = recovery_store->deserialize(entry)->apply();
1768 if (result == ccf::kv::ApplyResult::FAIL)
1769 {
1771 "Failed to deserialise private ledger entry: {}", result);
1772 // Note: rollback terms do not matter here as recovery store is
1773 // about to be discarded
1774 recovery_store->rollback({0, last_recovered_idx}, 0);
1775 recover_private_ledger_end_unsafe();
1776 return;
1777 }
1778 ++last_recovered_idx;
1779 }
1780 catch (const std::exception& e)
1781 {
1783 "Failed to deserialise private ledger entry: {}", e.what());
1784 recover_private_ledger_end_unsafe();
1785 return;
1786 }
1787
1789 {
1790 recovery_store->compact(last_recovered_idx);
1791 }
1792 }
1793
1794 if (recovery_store->current_version() == recovery_v)
1795 {
1796 LOG_INFO_FMT("Reached recovery final version at {}", recovery_v);
1797 recover_private_ledger_end_unsafe();
1798 }
1799 else
1800 {
1801 read_ledger_entries(
1802 last_recovered_idx + 1,
1803 std::min(last_recovered_idx + recovery_batch_size, recovery_v));
1804 }
1805 }
1806
1808 {
1809 // When reaching the end of the private ledger, make sure the same
1810 // ledger has been read and swap in private state
1811
1812 sm.expect(NodeStartupState::readingPrivateLedger);
1813
1815 "Try end private recovery at {}. Is primary: {}",
1816 recovery_v,
1817 consensus->is_primary());
1818
1819 if (recovery_v != recovery_store->current_version())
1820 {
1821 throw std::logic_error(fmt::format(
1822 "Private recovery did not reach public ledger seqno: {}/{}",
1823 recovery_store->current_version(),
1824 recovery_v));
1825 }
1826
1827 auto* h =
1828 dynamic_cast<MerkleTxHistory*>(recovery_store->get_history().get());
1829 if (h->get_replicated_state_root() != recovery_root)
1830 {
1831 throw std::logic_error(fmt::format(
1832 "Root of public store does not match root of private store at {}",
1833 recovery_v));
1834 }
1835
1836 network.tables->swap_private_maps(*recovery_store);
1837 recovery_store.reset();
1838
1839 // Raft should deserialise all security domains when network is opened
1840 consensus->enable_all_domains();
1841
1842 // Snapshots are only generated after recovery is complete
1843 snapshotter->set_snapshot_generation(true);
1844
1845 // Open the service
1846 if (consensus->can_replicate())
1847 {
1849 "Try end private recovery at {}. Trigger service opening",
1850 recovery_v);
1851
1852 auto tx = network.tables->create_tx();
1853
1854 {
1855 // Ensure this transition happens at-most-once, by checking that no
1856 // other node has already advanced the state
1857 auto* service = tx.ro<ccf::Service>(Tables::SERVICE);
1858 auto active_service = service->get();
1859
1860 if (!active_service.has_value())
1861 {
1862 throw std::logic_error(fmt::format(
1863 "Error in {}: no value in {}", __func__, Tables::SERVICE));
1864 }
1865
1866 if (
1867 active_service->status !=
1868 ServiceStatus::WAITING_FOR_RECOVERY_SHARES)
1869 {
1870 throw std::logic_error(fmt::format(
1871 "Error in {}: current service status is {}",
1872 __func__,
1873 active_service->status));
1874 }
1875 }
1876
1877 // Clear recovery shares that were submitted to initiate the recovery
1878 // procedure
1879 ShareManager::clear_submitted_recovery_shares(tx);
1880
1881 // Shares for the new ledger secret can only be issued now, once the
1882 // previous ledger secrets have been recovered
1883 share_manager.issue_recovery_shares(tx);
1884
1885 if (
1886 !InternalTablesAccess::open_service(tx) ||
1887 !InternalTablesAccess::endorse_previous_identity(
1888 tx, *network.identity->get_key_pair()))
1889 {
1890 throw std::logic_error("Service could not be opened");
1891 }
1892
1893 // Trigger a snapshot (at next signature) to ensure we have a working
1894 // snapshot signed by the current (now new) service identity, in case
1895 // we need to recover soon again.
1896 trigger_snapshot(tx);
1897
1898 if (tx.commit() != ccf::kv::CommitResult::SUCCESS)
1899 {
1900 throw std::logic_error(
1901 "Could not commit transaction when finishing network recovery");
1902 }
1903 }
1904 recovered_encrypted_ledger_secrets.clear();
1905 reset_data(quote_info.quote);
1906 reset_data(quote_info.endorsements);
1907 sm.advance(NodeStartupState::partOfNetwork);
1908 }
1909
1911 {
1912 // This hook is necessary to adjust the version at which the last ledger
1913 // secret before recovery is recorded in the store. This can only be
1914 // fired once, after the recovery shares for the post-recovery ledger
1915 // secret are issued.
1916 network.tables->set_map_hook(
1917 network.encrypted_ledger_secrets.get_name(),
1919 [this](
1920 ccf::kv::Version version,
1923 if (!w.has_value())
1924 {
1925 throw std::logic_error(fmt::format(
1926 "Unexpected removal from {} table",
1927 network.encrypted_ledger_secrets.get_name()));
1928 }
1929
1930 network.ledger_secrets->adjust_previous_secret_stored_version(
1931 version);
1932
1933 network.tables->unset_map_hook(
1934 network.encrypted_ledger_secrets.get_name());
1935
1936 return {nullptr};
1937 }));
1938 }
1939
1940 //
1941 // funcs in state "readingPublicLedger" or "readingPrivateLedger"
1942 //
1944 {
1945 std::lock_guard<pal::Mutex> guard(lock);
1946
1947 if (is_reading_public_ledger())
1948 {
1949 recover_public_ledger_end_unsafe();
1950 }
1951 else if (is_reading_private_ledger())
1952 {
1953 recover_private_ledger_end_unsafe();
1954 }
1955 else
1956 {
1958 "Node in state {} cannot finalise ledger recovery", sm.value());
1959 return;
1960 }
1961 }
1962
1963 //
1964 // funcs in state "partOfPublicNetwork"
1965 //
1967 {
1968 recovery_store = std::make_shared<ccf::kv::Store>(
1969 true /* Check transactions in order */,
1970 true /* Make use of historical secrets */);
1971 auto recovery_history = std::make_shared<MerkleTxHistory>(
1972 *recovery_store,
1973 self,
1974 *node_sign_kp,
1975 sig_tx_interval,
1976 sig_ms_interval,
1977 false /* No signature timer on recovery_history */);
1978
1979 auto recovery_encryptor = make_encryptor();
1980
1981 recovery_store->set_history(recovery_history);
1982 recovery_store->set_encryptor(recovery_encryptor);
1983
1984 // Record real store version and root
1985 recovery_v = network.tables->current_version();
1986 auto* h = dynamic_cast<MerkleTxHistory*>(history.get());
1987 recovery_root = h->get_replicated_state_root();
1988
1989 if (startup_snapshot_info)
1990 {
1991 std::vector<ccf::kv::Version> view_history_;
1993 deserialise_snapshot(
1994 recovery_store,
1995 startup_snapshot_info->raw,
1996 hooks,
1997 &view_history_,
1998 false);
1999 startup_snapshot_info.reset();
2000 }
2001
2003 "Recovery store successfully setup at {}. Target recovery seqno: {}",
2004 recovery_store->current_version(),
2005 recovery_v);
2006 }
2007
2009 {
2010 share_manager.shuffle_recovery_shares(tx);
2011 }
2012
2014 {
2015 auto* tx_ = dynamic_cast<ccf::kv::CommittableTx*>(&tx);
2016 if (tx_ == nullptr)
2017 {
2018 throw std::logic_error("Could not cast tx to CommittableTx");
2019 }
2020 tx_->set_tx_flag(
2022 }
2023
2025 {
2026 auto* committable_tx = dynamic_cast<ccf::kv::CommittableTx*>(&tx);
2027 if (committable_tx == nullptr)
2028 {
2029 throw std::logic_error("Could not cast tx to CommittableTx");
2030 }
2031 committable_tx->set_tx_flag(
2033 }
2034
2036 ccf::kv::Tx& tx,
2038 {
2039 std::lock_guard<pal::Mutex> guard(lock);
2040
2041 auto* service = tx.rw<Service>(Tables::SERVICE);
2042 auto service_info = service->get();
2043 if (!service_info.has_value())
2044 {
2045 throw std::logic_error(
2046 "Service information cannot be found to transition service to "
2047 "open");
2048 }
2049
2050 // Idempotence: if the service is already open or waiting for recovery
2051 // shares, this function should succeed with no effect
2052 if (
2053 service_info->status == ServiceStatus::WAITING_FOR_RECOVERY_SHARES ||
2054 service_info->status == ServiceStatus::OPEN)
2055 {
2057 "Service in state {} is already open", service_info->status);
2058 return;
2059 }
2060
2061 if (service_info->status == ServiceStatus::RECOVERING)
2062 {
2063 const auto prev_ident =
2064 tx.ro<PreviousServiceIdentity>(Tables::PREVIOUS_SERVICE_IDENTITY)
2065 ->get();
2066 if (!prev_ident.has_value() || !identities.previous.has_value())
2067 {
2068 throw std::logic_error(
2069 "Recovery with service certificates requires both, a previous "
2070 "service identity written to the KV during recovery genesis and a "
2071 "transition_service_to_open proposal that contains previous and "
2072 "next service certificates");
2073 }
2074
2075 const ccf::crypto::Pem from_proposal(
2076 identities.previous->data(), identities.previous->size());
2077 if (prev_ident.value() != from_proposal)
2078 {
2079 throw std::logic_error(fmt::format(
2080 "Previous service identity does not match.\nActual:\n{}\nIn "
2081 "proposal:\n{}",
2082 prev_ident->str(),
2083 from_proposal.str()));
2084 }
2085 }
2086
2087 if (identities.next != service_info->cert)
2088 {
2089 throw std::logic_error(fmt::format(
2090 "Service identity mismatch: the next service identity in the "
2091 "transition_service_to_open proposal does not match the current "
2092 "service identity:\nNext:\n{}\nCurrent:\n{}",
2093 identities.next.str(),
2094 service_info->cert.str()));
2095 }
2096
2097 if (is_part_of_public_network())
2098 {
2099 // If the node is in public mode, start accepting member recovery
2100 // shares
2101 ShareManager::clear_submitted_recovery_shares(tx);
2102 service_info->status = ServiceStatus::WAITING_FOR_RECOVERY_SHARES;
2103 service->put(service_info.value());
2104 if (
2105 config.sealing_recovery.has_value() &&
2106 !config.sealing_recovery->location.name.empty())
2107 {
2108 if (!cached_sealed_recovery_data.has_value())
2109 {
2110 throw std::logic_error(
2111 "Missing cached sealed recovery key for private recovery");
2112 }
2113
2114 auto& [last_sealed_node_id, last_sealed_wrapping_key, last_sealed_recovery_key] =
2115 cached_sealed_recovery_data.value();
2116 auto unsealed_ls = sealing::unseal_share(
2117 tx, last_sealed_wrapping_key, last_sealed_recovery_key);
2118 if (unsealed_ls.has_value())
2119 {
2120 tx.wo<LastRecoveryType>(Tables::LAST_RECOVERY_TYPE)
2121 ->put(RecoveryType::LOCAL_UNSEALING);
2122 LOG_INFO_FMT("Unsealed ledger secret, initiating private recovery");
2123 initiate_private_recovery_unsealing_unsafe(tx, unsealed_ls.value());
2124 }
2125 else
2126 {
2127 throw std::logic_error(
2128 "Failed to unseal ledger secret for private recovery");
2129 }
2130 }
2131 else
2132 {
2133 tx.wo<LastRecoveryType>(Tables::LAST_RECOVERY_TYPE)
2134 ->put(RecoveryType::RECOVERY_SHARES);
2135 }
2136 return;
2137 }
2138
2139 if (is_part_of_network())
2140 {
2141 // Otherwise, if the node is part of the network. Open the network
2142 // straight away. Recovery shares are allocated to each recovery
2143 // member.
2144 try
2145 {
2146 share_manager.issue_recovery_shares(tx);
2147 }
2148 catch (const std::logic_error& e)
2149 {
2150 throw std::logic_error(
2151 fmt::format("Failed to issue recovery shares: {}", e.what()));
2152 }
2153
2154 InternalTablesAccess::open_service(tx);
2155 InternalTablesAccess::endorse_previous_identity(
2156 tx, *network.identity->get_key_pair());
2157 trigger_snapshot(tx);
2158 return;
2159 }
2160
2161 throw std::logic_error(
2162 fmt::format("Node in state {} cannot open service", sm.value()));
2163 }
2164
2166 {
2167 std::lock_guard<pal::Mutex> guard(lock);
2168 sm.expect(NodeStartupState::partOfPublicNetwork);
2169 LedgerSecretsMap recovered_ledger_secrets =
2170 share_manager.restore_recovery_shares_info(
2171 tx, recovered_encrypted_ledger_secrets);
2172 initiate_private_recovery_unsafe(tx, recovered_ledger_secrets);
2173 }
2174
2176 ccf::kv::Tx& tx, const LedgerSecretPtr& unsealed_ledger_secret)
2177 {
2178 sm.expect(NodeStartupState::partOfPublicNetwork);
2179 LedgerSecretsMap recovered_ledger_secrets =
2180 share_manager.restore_ledger_secrets_map(
2181 tx, recovered_encrypted_ledger_secrets, unsealed_ledger_secret);
2182 initiate_private_recovery_unsafe(tx, recovered_ledger_secrets);
2183 }
2184
2185 // Decrypts chain of ledger secrets, and writes those to the ledger
2186 // encrypted for each node. On a commit hook for this write, each node
2187 // (including this one!) will begin_private_recovery().
2189 ccf::kv::Tx& tx, LedgerSecretsMap recovered_ledger_secrets)
2190 {
2191 // Broadcast decrypted ledger secrets to other nodes for them to
2192 // initiate private recovery too
2193 LedgerSecretsBroadcast::broadcast_some(
2194 InternalTablesAccess::get_trusted_nodes(tx),
2195 tx.wo(network.secrets),
2196 recovered_ledger_secrets);
2197 }
2198
2199 //
2200 // funcs in state "partOfNetwork" or "partOfPublicNetwork"
2201 //
2202 void tick(std::chrono::milliseconds elapsed)
2203 {
2204 if (
2205 !sm.check(NodeStartupState::partOfNetwork) &&
2206 !sm.check(NodeStartupState::partOfPublicNetwork) &&
2207 !sm.check(NodeStartupState::readingPrivateLedger))
2208 {
2209 return;
2210 }
2211
2212 consensus->periodic(elapsed);
2213
2214 if (sm.check(NodeStartupState::partOfNetwork))
2215 {
2216 const auto tx_id = consensus->get_committed_txid();
2217 indexer->update_strategies(elapsed, {tx_id.first, tx_id.second});
2218 }
2219
2220 n2n_channels->tick(elapsed);
2221 }
2222
2224 {
2225 if (
2226 !sm.check(NodeStartupState::partOfNetwork) &&
2227 !sm.check(NodeStartupState::partOfPublicNetwork) &&
2228 !sm.check(NodeStartupState::readingPrivateLedger))
2229 {
2230 return;
2231 }
2232
2233 consensus->periodic_end();
2234 }
2235
2236 void stop_notice() override
2237 {
2238 consensus->nominate_successor();
2239 stop_noticed = true;
2240 }
2241
2243 {
2244 return stop_noticed;
2245 }
2246
2247 void recv_node_inbound(const uint8_t* data, size_t size)
2248 {
2249 auto [msg_type, from, payload] =
2250 ringbuffer::read_message<node_inbound>(data, size);
2251
2252 const auto* payload_data = payload.data;
2253 auto payload_size = payload.size;
2254
2255 if (msg_type == NodeMsgType::forwarded_msg)
2256 {
2257 cmd_forwarder->recv_message(from, payload_data, payload_size);
2258 }
2259 else
2260 {
2261 // Only process messages once part of network
2262 if (
2263 !sm.check(NodeStartupState::partOfNetwork) &&
2264 !sm.check(NodeStartupState::partOfPublicNetwork) &&
2265 !sm.check(NodeStartupState::readingPrivateLedger))
2266 {
2268 "Ignoring node msg received too early - current state is {}",
2269 sm.value());
2270 return;
2271 }
2272
2273 switch (msg_type)
2274 {
2275 case forwarded_msg:
2276 {
2277 LOG_FAIL_FMT("Unexpected forwarded_msg in recv_node_inbound");
2278 return;
2279 }
2280 case channel_msg:
2281 {
2282 n2n_channels->recv_channel_message(
2283 from, payload_data, payload_size);
2284 return;
2285 }
2286
2287 case consensus_msg:
2288 {
2289 consensus->recv_message(from, payload_data, payload_size);
2290 return;
2291 }
2292 default:
2293 {
2294 throw std::logic_error(fmt::format(
2295 "Unknown node message type: {}",
2296 static_cast<uint32_t>(msg_type)));
2297 }
2298 }
2299 }
2300 }
2301
2302 //
2303 // always available
2304 //
2305 [[nodiscard]] bool is_primary() const override
2306 {
2307 return (
2308 (sm.check(NodeStartupState::partOfNetwork) ||
2309 sm.check(NodeStartupState::partOfPublicNetwork) ||
2310 sm.check(NodeStartupState::readingPrivateLedger)) &&
2311 consensus->is_primary());
2312 }
2313
2314 bool can_replicate() override
2315 {
2316 return (
2317 (sm.check(NodeStartupState::partOfNetwork) ||
2318 sm.check(NodeStartupState::partOfPublicNetwork) ||
2319 sm.check(NodeStartupState::readingPrivateLedger)) &&
2320 consensus->can_replicate());
2321 }
2322
2323 std::optional<ccf::NodeId> get_primary() override
2324 {
2325 return consensus->primary();
2326 }
2327
2328 [[nodiscard]] bool is_in_initialised_state() const override
2329 {
2330 return sm.check(NodeStartupState::initialized);
2331 }
2332
2333 [[nodiscard]] bool is_part_of_network() const override
2334 {
2335 return sm.check(NodeStartupState::partOfNetwork);
2336 }
2337
2338 [[nodiscard]] bool is_reading_public_ledger() const override
2339 {
2340 return sm.check(NodeStartupState::readingPublicLedger);
2341 }
2342
2343 [[nodiscard]] bool is_reading_private_ledger() const override
2344 {
2345 return sm.check(NodeStartupState::readingPrivateLedger);
2346 }
2347
2348 [[nodiscard]] bool is_part_of_public_network() const override
2349 {
2350 return sm.check(NodeStartupState::partOfPublicNetwork);
2351 }
2352
2353 [[nodiscard]] bool is_accessible_to_members() const override
2354 {
2355 const auto val = sm.value();
2356 return val == NodeStartupState::partOfNetwork ||
2357 val == NodeStartupState::partOfPublicNetwork ||
2358 val == NodeStartupState::readingPrivateLedger;
2359 }
2360
2362 {
2363 std::lock_guard<pal::Mutex> guard(lock);
2364 auto s = sm.value();
2365 if (s == NodeStartupState::readingPrivateLedger)
2366 {
2367 return {s, recovery_v, recovery_store->current_version()};
2368 }
2369
2370 return {s, std::nullopt, std::nullopt};
2371 }
2372
2373 bool rekey_ledger(ccf::kv::Tx& tx) override
2374 {
2375 std::lock_guard<pal::Mutex> guard(lock);
2376 sm.expect(NodeStartupState::partOfNetwork);
2377
2378 // The ledger should not be re-keyed when the service is not open
2379 // because:
2380 // - While waiting for recovery shares, the submitted shares are stored
2381 // in a public table, encrypted with the ledger secret generated at
2382 // startup of the first recovery node
2383 // - On recovery, historical ledger secrets can only be looked up in the
2384 // ledger once all ledger secrets have been restored
2385 const auto service_status = InternalTablesAccess::get_service_status(tx);
2386 if (
2387 !service_status.has_value() ||
2388 service_status.value() != ServiceStatus::OPEN)
2389 {
2390 LOG_FAIL_FMT("Cannot rekey ledger while the service is not open");
2391 return false;
2392 }
2393
2394 // Effects of ledger rekey are only observed from the next transaction,
2395 // once the local hook on the secrets table has been triggered.
2396
2397 auto new_ledger_secret = make_ledger_secret();
2398 share_manager.issue_recovery_shares(tx, new_ledger_secret);
2399 LedgerSecretsBroadcast::broadcast_new(
2400 InternalTablesAccess::get_trusted_nodes(tx),
2401 tx.wo(network.secrets),
2402 std::move(new_ledger_secret));
2403
2404 return true;
2405 }
2406
2407 [[nodiscard]] NodeId get_node_id() const
2408 {
2409 return self;
2410 }
2411
2413 {
2414 std::lock_guard<pal::Mutex> guard(lock);
2415 return startup_seqno;
2416 }
2417
2419 {
2420 return rpcsessions->get_session_metrics();
2421 }
2422
2424 {
2425 std::lock_guard<pal::Mutex> guard(lock);
2426 return self_signed_node_cert;
2427 }
2428
2430 {
2431 if (history == nullptr)
2432 {
2433 throw std::logic_error(
2434 "Attempting to access COSE signatures config before history has been "
2435 "constructed");
2436 }
2437
2438 return history->get_cose_signatures_config();
2439 }
2440
2441 private:
2442 bool is_ip(const std::string_view& hostname)
2443 {
2444 // IP address components are purely numeric. DNS names may be largely
2445 // numeric, but at least the final component (TLD) must not be
2446 // all-numeric. So this distinguishes "1.2.3.4" (an IP address) from
2447 // "1.2.3.c4m" (a DNS name). "1.2.3." is invalid for either, and will
2448 // throw. Attempts to handle IPv6 by also splitting on ':', but this is
2449 // untested.
2450 const auto final_component =
2451 ccf::nonstd::split(ccf::nonstd::split(hostname, ".").back(), ":")
2452 .back();
2453 if (final_component.empty())
2454 {
2455 throw std::runtime_error(fmt::format(
2456 "{} has a trailing period, is not a valid hostname", hostname));
2457 }
2458
2459 return std::ranges::all_of(
2460 final_component, [](char c) { return c >= '0' && c <= '9'; });
2461 }
2462
2463 std::vector<ccf::crypto::SubjectAltName> get_subject_alternative_names()
2464 {
2465 // If no Subject Alternative Name (SAN) is passed in at node creation,
2466 // default to using node's RPC address as single SAN. Otherwise, use
2467 // specified SANs.
2468 if (!config.node_certificate.subject_alt_names.empty())
2469 {
2470 return ccf::crypto::sans_from_string_list(
2472 }
2473
2474 // Construct SANs from RPC interfaces, manually detecting whether each
2475 // is a domain name or IP
2476 std::vector<ccf::crypto::SubjectAltName> sans;
2477 for (const auto& [_, interface] : config.network.rpc_interfaces)
2478 {
2479 auto host = split_net_address(interface.published_address).first;
2480 sans.push_back({host, is_ip(host)});
2481 }
2482 return sans;
2483 }
2484
2485 void accept_node_tls_connections()
2486 {
2487 // Accept TLS connections, presenting self-signed (i.e. non-endorsed)
2488 // node certificate.
2489 rpcsessions->set_node_cert(
2490 self_signed_node_cert, node_sign_kp->private_key_pem());
2491 LOG_INFO_FMT("Node TLS connections now accepted");
2492 }
2493
2494 void accept_network_tls_connections()
2495 {
2496 // Accept TLS connections, presenting node certificate signed by network
2497 // certificate
2499 endorsed_node_cert.has_value(),
2500 "Node certificate should be endorsed before accepting endorsed "
2501 "client "
2502 "connections");
2503 if (auto cert_opt = endorsed_node_cert; cert_opt.has_value())
2504 {
2505 const auto& endorsed_cert = cert_opt.value();
2506 rpcsessions->set_network_cert(
2507 endorsed_cert, node_sign_kp->private_key_pem());
2508 }
2509 LOG_INFO_FMT("Network TLS connections now accepted");
2510 }
2511
2512 auto find_frontend(ActorsType actor)
2513 {
2514 auto fe = rpc_map->find(actor);
2515 if (!fe.has_value())
2516 {
2517 throw std::logic_error(
2518 fmt::format("Cannot find {} frontend", (int)actor));
2519 }
2520 return fe.value();
2521 }
2522
2523 void open_frontend(ActorsType actor)
2524 {
2525 find_frontend(actor)->open();
2526 }
2527
2528 void open_user_frontend()
2529 {
2530 open_frontend(ActorsType::users);
2531 }
2532
2533 bool is_member_frontend_open_unsafe()
2534 {
2535 return find_frontend(ActorsType::members)->is_open();
2536 }
2537
2538 bool is_member_frontend_open() override
2539 {
2540 std::lock_guard<pal::Mutex> guard(lock);
2541 return is_member_frontend_open_unsafe();
2542 }
2543
2544 bool is_user_frontend_open() override
2545 {
2546 std::lock_guard<pal::Mutex> guard(lock);
2547 return find_frontend(ActorsType::users)->is_open();
2548 }
2549
2550 std::vector<uint8_t> serialize_create_request(
2551 View create_view, bool create_consortium = true)
2552 {
2553 CreateNetworkNodeToNode::In create_params;
2554
2555 // False on recovery where the consortium is read from the existing
2556 // ledger
2557 if (create_consortium)
2558 {
2559 create_params.genesis_info = config.start;
2560 }
2561
2562 create_params.node_id = self;
2563 create_params.certificate_signing_request = node_sign_kp->create_csr(
2564 config.node_certificate.subject_name, subject_alt_names);
2565 create_params.node_endorsed_certificate =
2566 ccf::crypto::create_endorsed_cert(
2567 create_params.certificate_signing_request,
2568 config.startup_host_time,
2570 network.identity->priv_key,
2571 network.identity->cert);
2572
2573 // Even though endorsed certificate is updated on (global) hook, history
2574 // requires it to generate signatures
2575 history->set_endorsed_certificate(
2576 create_params.node_endorsed_certificate);
2577
2578 create_params.public_key = node_sign_kp->public_key_pem();
2579 create_params.service_cert = network.identity->cert;
2580 create_params.quote_info = quote_info;
2581 create_params.public_encryption_key = node_encrypt_kp->public_key_pem();
2582 create_params.measurement = node_measurement;
2583 create_params.snp_uvm_endorsements = snp_uvm_endorsements;
2584 create_params.snp_security_policy =
2586
2587 create_params.node_info_network = config.network;
2588 create_params.node_data = config.node_data;
2589 create_params.service_data = config.service_data;
2590 create_params.create_txid = {create_view, last_recovered_signed_idx + 1};
2591
2592 if (config.sealing_recovery.has_value() && snp_tcb_version.has_value())
2593 {
2594 create_params.sealing_recovery_data = std::make_pair(
2595 sealing::get_snp_sealed_recovery_key(snp_tcb_version.value()),
2596 config.sealing_recovery->location.name);
2597 }
2598
2599 const auto body = nlohmann::json(create_params).dump();
2600
2601 ::http::Request request(
2602 fmt::format("/{}/{}", get_actor_prefix(ActorsType::nodes), "create"));
2603 request.set_header(
2604 ccf::http::headers::CONTENT_TYPE,
2605 ccf::http::headervalues::contenttype::JSON);
2606
2607 request.set_body(body);
2608
2609 return request.build_request();
2610 }
2611
2612 bool extract_create_result(const std::shared_ptr<RpcContext>& ctx)
2613 {
2614 if (ctx == nullptr)
2615 {
2616 LOG_FAIL_FMT("Expected non-null context");
2617 return false;
2618 }
2619
2620 const auto status = ctx->get_response_status();
2621 const auto& raw_body = ctx->get_response_body();
2622 if (status != HTTP_STATUS_OK)
2623 {
2625 "Create response is error: {} {}\n{}",
2626 status,
2627 ccf::http_status_str((ccf::http_status)status),
2628 std::string(raw_body.begin(), raw_body.end()));
2629 return false;
2630 }
2631
2632 const auto body = nlohmann::json::parse(raw_body);
2633 if (!body.is_boolean())
2634 {
2635 LOG_FAIL_FMT("Expected boolean body in create response");
2637 "Expected boolean body in create response: {}", body.dump());
2638 return false;
2639 }
2640
2641 return body;
2642 }
2643
2644 bool send_create_request(const std::vector<uint8_t>& packed)
2645 {
2646 auto node_session = std::make_shared<SessionContext>(
2647 InvalidSessionId, self_signed_node_cert.raw());
2648 auto ctx = make_rpc_context(node_session, packed);
2649
2650 std::shared_ptr<ccf::RpcHandler> search =
2651 ::http::fetch_rpc_handler(ctx, this->rpc_map);
2652
2653 search->process(ctx);
2654
2655 return extract_create_result(ctx);
2656 }
2657
2658 void create_and_send_boot_request(
2659 View create_view, bool create_consortium = true)
2660 {
2661 // Service creation transaction is asynchronous to avoid deadlocks
2662 // (e.g. https://github.com/microsoft/CCF/issues/3788)
2664 ccf::tasks::make_basic_task([this, create_view, create_consortium]() {
2665 if (!this->send_create_request(
2666 this->serialize_create_request(create_view, create_consortium)))
2667 {
2668 throw std::runtime_error(
2669 "Service creation request could not be committed");
2670 }
2671 if (create_consortium)
2672 {
2673 this->advance_part_of_network();
2674 }
2675 else
2676 {
2677 this->advance_part_of_public_network();
2678 }
2679 }));
2680 }
2681
2682 void begin_private_recovery()
2683 {
2684 sm.expect(NodeStartupState::partOfPublicNetwork);
2685
2686 LOG_INFO_FMT("Beginning private recovery");
2687
2688 setup_private_recovery_store();
2689
2690 reset_recovery_hook();
2691 setup_one_off_secret_hook();
2692
2693 // Start reading private security domain of ledger
2694 sm.advance(NodeStartupState::readingPrivateLedger);
2695 last_recovered_idx = recovery_store->current_version();
2696 read_ledger_entries(
2697 last_recovered_idx + 1, last_recovered_idx + recovery_batch_size);
2698 }
2699
2700 void setup_basic_hooks()
2701 {
2702 network.tables->set_map_hook(
2703 network.secrets.get_name(),
2705 [this](ccf::kv::Version hook_version, const Secrets::Write& w)
2707 // Used to rekey the ledger on a live service
2708 if (!is_part_of_network())
2709 {
2710 // Ledger rekey is not allowed during recovery
2711 return {nullptr};
2712 }
2713
2714 const auto& ledger_secrets_for_nodes = w;
2715 if (!ledger_secrets_for_nodes.has_value())
2716 {
2717 throw std::logic_error(fmt::format(
2718 "Unexpected removal from {} table",
2719 network.secrets.get_name()));
2720 }
2721
2722 for (const auto& [node_id, encrypted_ledger_secrets] :
2723 ledger_secrets_for_nodes.value())
2724 {
2725 if (node_id != self)
2726 {
2727 // Only consider ledger secrets for this node
2728 continue;
2729 }
2730
2731 for (const auto& encrypted_ledger_secret :
2732 encrypted_ledger_secrets)
2733 {
2734 auto plain_ledger_secret = LedgerSecretsBroadcast::decrypt(
2735 node_encrypt_kp, encrypted_ledger_secret.encrypted_secret);
2736
2737 // When rekeying, set the encryption key for the next version
2738 // onward (backups deserialise this transaction with the
2739 // previous ledger secret)
2740 auto ledger_secret = std::make_shared<LedgerSecret>(
2741 std::move(plain_ledger_secret), hook_version);
2742 network.ledger_secrets->set_secret(
2743 hook_version + 1, std::move(ledger_secret));
2744 }
2745 }
2746
2747 return {nullptr};
2748 }));
2749
2750 network.tables->set_global_hook(
2751 network.secrets.get_name(),
2753 ccf::kv::Version hook_version,
2754 const Secrets::Write& w) {
2755 // Used on recovery to initiate private recovery on all nodes.
2756 if (!is_part_of_public_network())
2757 {
2758 return;
2759 }
2760
2761 const auto& ledger_secrets_for_nodes = w;
2762 if (!ledger_secrets_for_nodes.has_value())
2763 {
2764 throw std::logic_error(fmt::format(
2765 "Unexpected removal from {} table", network.secrets.get_name()));
2766 }
2767
2768 for (const auto& [node_id, encrypted_ledger_secrets] :
2769 ledger_secrets_for_nodes.value())
2770 {
2771 if (node_id != self)
2772 {
2773 // Only consider ledger secrets for this node
2774 continue;
2775 }
2776
2777 LedgerSecretsMap restored_ledger_secrets = {};
2778 for (const auto& encrypted_ledger_secret : encrypted_ledger_secrets)
2779 {
2780 // On rekey, the version is inferred from the version at which
2781 // the hook is executed. Otherwise, on recovery, use the
2782 // version read from the write set.
2783 if (!encrypted_ledger_secret.version.has_value())
2784 {
2785 throw std::logic_error(fmt::format(
2786 "Commit hook at seqno {} for table {}: no version for "
2787 "encrypted ledger secret",
2788 hook_version,
2789 network.secrets.get_name()));
2790 }
2791
2792 auto plain_ledger_secret = LedgerSecretsBroadcast::decrypt(
2793 node_encrypt_kp, encrypted_ledger_secret.encrypted_secret);
2794
2795 restored_ledger_secrets.emplace(
2796 encrypted_ledger_secret.version.value(),
2797 std::make_shared<LedgerSecret>(
2798 std::move(plain_ledger_secret),
2799 encrypted_ledger_secret.previous_secret_stored_version));
2800 }
2801
2802 if (!restored_ledger_secrets.empty())
2803 {
2804 // When recovering, restore ledger secrets and trigger end of
2805 // recovery protocol (backup only)
2806 network.ledger_secrets->restore_historical(
2807 std::move(restored_ledger_secrets));
2808 begin_private_recovery();
2809 return;
2810 }
2811 }
2812
2814 "Found no ledger secrets for this node ({}) in global commit hook "
2815 "for {} @ {}",
2816 self,
2817 network.secrets.get_name(),
2818 hook_version);
2819 }));
2820
2821 network.tables->set_global_hook(
2822 network.nodes.get_name(),
2824 [this](ccf::kv::Version hook_version, const Nodes::Write& w) {
2825 std::vector<NodeId> retired_committed_nodes;
2826 for (const auto& [node_id, node_info] : w)
2827 {
2828 if (node_info.has_value() && node_info->retired_committed)
2829 {
2830 retired_committed_nodes.push_back(node_id);
2831 }
2832 }
2833 consensus->set_retired_committed(
2834 hook_version, retired_committed_nodes);
2835 }));
2836
2837 // Service-endorsed certificate is passed to history as early as _local_
2838 // commit since a new node may become primary (and thus, e.g. generate
2839 // signatures) before the transaction that added it is _globally_
2840 // committed (see https://github.com/microsoft/CCF/issues/4063). It is OK
2841 // if this transaction is rolled back as the node will no longer be part
2842 // of the service.
2843 network.tables->set_map_hook(
2844 network.node_endorsed_certificates.get_name(),
2846 [this](
2847 ccf::kv::Version hook_version,
2851 "[local] node_endorsed_certificates local hook at version {}, "
2852 "with {} writes",
2853 hook_version,
2854 w.size());
2855 for (auto const& [node_id, endorsed_certificate] : w)
2856 {
2857 if (node_id != self)
2858 {
2860 "[local] Ignoring endorsed certificate for other node {}",
2861 node_id);
2862 continue;
2863 }
2864
2865 if (!endorsed_certificate.has_value())
2866 {
2868 "[local] Endorsed cert for self ({}) has been deleted", self);
2869 throw std::logic_error(fmt::format(
2870 "Could not find endorsed node certificate for {}", self));
2871 }
2872
2873 std::lock_guard<pal::Mutex> guard(lock);
2874
2875 if (endorsed_node_cert.has_value())
2876 {
2878 "[local] Previous endorsed node cert was:\n{}",
2879 endorsed_node_cert->str());
2880 }
2881
2882 endorsed_node_cert = endorsed_certificate.value();
2884 "[local] Under lock, setting endorsed node cert to:\n{}",
2885 endorsed_node_cert->str());
2886 history->set_endorsed_certificate(endorsed_node_cert.value());
2887 n2n_channels->set_endorsed_node_cert(endorsed_node_cert.value());
2888 }
2889
2890 return {nullptr};
2891 }));
2892
2893 network.tables->set_global_hook(
2894 network.node_endorsed_certificates.get_name(),
2896 [this](
2897 ccf::kv::Version hook_version,
2900 "[global] node_endorsed_certificates global hook at version {}, "
2901 "with {} writes",
2902 hook_version,
2903 w.size());
2904 for (auto const& [node_id, endorsed_certificate] : w)
2905 {
2906 if (node_id != self)
2907 {
2909 "[global] Ignoring endorsed certificate for other node {}",
2910 node_id);
2911 continue;
2912 }
2913
2914 if (!endorsed_certificate.has_value())
2915 {
2917 "[global] Endorsed cert for self ({}) has been deleted",
2918 self);
2919 throw std::logic_error(fmt::format(
2920 "Could not find endorsed node certificate for {}", self));
2921 }
2922
2923 std::lock_guard<pal::Mutex> guard(lock);
2924
2925 LOG_INFO_FMT("[global] Accepting network connections");
2926 accept_network_tls_connections();
2927
2928 if (is_member_frontend_open_unsafe())
2929 {
2930 // Also, automatically refresh self-signed node certificate,
2931 // using the same validity period as the endorsed certificate.
2932 // Note that this is only done when the certificate is renewed
2933 // via proposal (i.e. when the member frontend is open), and not
2934 // for the initial addition of the node (the self-signed
2935 // certificate is output to disk then).
2936 auto [valid_from, valid_to] =
2937 ccf::crypto::make_verifier(endorsed_node_cert.value())
2938 ->validity_period();
2940 "[global] Member frontend is open, so refreshing self-signed "
2941 "node cert");
2943 "[global] Previously:\n{}", self_signed_node_cert.str());
2944 self_signed_node_cert = create_self_signed_cert(
2945 node_sign_kp,
2947 subject_alt_names,
2948 valid_from,
2949 valid_to);
2950 LOG_INFO_FMT("[global] Now:\n{}", self_signed_node_cert.str());
2951
2952 LOG_INFO_FMT("[global] Accepting node connections");
2953 accept_node_tls_connections();
2954 }
2955 else
2956 {
2957 LOG_INFO_FMT("[global] Member frontend is NOT open");
2959 "[global] Self-signed node cert remains:\n{}",
2960 self_signed_node_cert.str());
2961 }
2962
2963 LOG_INFO_FMT("[global] Opening members frontend");
2964 open_frontend(ActorsType::members);
2965 }
2966 }));
2967
2968 network.tables->set_global_hook(
2969 network.service.get_name(),
2971 [this](ccf::kv::Version hook_version, const Service::Write& w) {
2972 if (!w.has_value())
2973 {
2974 throw std::logic_error("Unexpected deletion in service value");
2975 }
2976
2977 // Service open on historical service has no effect
2978 auto hook_pubk_pem = ccf::crypto::public_key_pem_from_cert(
2980 auto current_pubk_pem =
2981 ccf::crypto::make_ec_key_pair(network.identity->priv_key)
2982 ->public_key_pem();
2983 if (hook_pubk_pem != current_pubk_pem)
2984 {
2986 "Ignoring historical service open at seqno {} for {}",
2987 hook_version,
2988 w->cert.str());
2989 return;
2990 }
2991
2993 "Executing global hook for service table at {}, to service "
2994 "status {}. Cert is:\n{}",
2995 hook_version,
2996 w->status,
2997 w->cert.str());
2998
2999 network.identity->set_certificate(w->cert);
3000 if (w->status == ServiceStatus::OPEN)
3001 {
3002 open_user_frontend();
3003
3004 RINGBUFFER_WRITE_MESSAGE(::consensus::ledger_open, to_host);
3005 LOG_INFO_FMT("Service open at seqno {}", hook_version);
3006 }
3007 }));
3008 }
3009
3010 ccf::kv::Version get_last_recovered_signed_idx() override
3011 {
3012 // On recovery, only one node recovers the public ledger and is thus
3013 // aware of the version at which the new ledger secret is applicable
3014 // from. If the primary changes while the network is public-only, the
3015 // new primary should also know at which version the new ledger secret
3016 // is applicable from.
3017 std::lock_guard<pal::Mutex> guard(lock);
3018 return last_recovered_signed_idx;
3019 }
3020
3021 void setup_recovery_hook()
3022 {
3023 network.tables->set_map_hook(
3024 network.encrypted_ledger_secrets.get_name(),
3026 [this](
3027 ccf::kv::Version version,
3030 auto encrypted_ledger_secret_info = w;
3031 if (!encrypted_ledger_secret_info.has_value())
3032 {
3033 throw std::logic_error(fmt::format(
3034 "Unexpected removal from {} table",
3035 network.encrypted_ledger_secrets.get_name()));
3036 }
3037
3038 // If the version of the next ledger secret is not set, deduce it
3039 // from the hook version (i.e. ledger rekey)
3040 if (!encrypted_ledger_secret_info->next_version.has_value())
3041 {
3042 encrypted_ledger_secret_info->next_version = version + 1;
3043 }
3044
3045 if (encrypted_ledger_secret_info->previous_ledger_secret
3046 .has_value())
3047 {
3048 LOG_DEBUG_FMT(
3049 "Recovering encrypted ledger secret valid at seqno {}",
3050 encrypted_ledger_secret_info->previous_ledger_secret->version);
3051 }
3052
3053 recovered_encrypted_ledger_secrets.emplace_back(
3054 std::move(encrypted_ledger_secret_info.value()));
3055
3056 return {nullptr};
3057 }));
3058 }
3059
3060 void reset_recovery_hook()
3061 {
3062 network.tables->unset_map_hook(
3063 network.encrypted_ledger_secrets.get_name());
3064 }
3065
3066 void setup_n2n_channels(
3067 const std::optional<ccf::crypto::Pem>& endorsed_node_certificate_ =
3068 std::nullopt)
3069 {
3070 // If the endorsed node certificate is available at the time the
3071 // consensus/node-to-node channels are initialised, use it (i.e. join).
3072 // Otherwise, specify it later, on endorsed certificate table hook (i.e.
3073 // start or recover).
3074 n2n_channels->initialize(
3075 self, network.identity->cert, node_sign_kp, endorsed_node_certificate_);
3076 }
3077
3078 void setup_cmd_forwarder()
3079 {
3080 cmd_forwarder->initialize(self);
3081 }
3082
3083 void setup_history()
3084 {
3085 if (history)
3086 {
3087 throw std::logic_error("History already initialised");
3088 }
3089
3090 history = std::make_shared<MerkleTxHistory>(
3091 *network.tables,
3092 self,
3093 *node_sign_kp,
3094 sig_tx_interval,
3095 sig_ms_interval,
3096 false /* start timed signatures after first tx */);
3097 network.tables->set_history(history);
3098 }
3099
3100 void setup_encryptor()
3101 {
3102 if (encryptor)
3103 {
3104 throw std::logic_error("Encryptor already initialised");
3105 }
3106
3107 encryptor = make_encryptor();
3108 network.tables->set_encryptor(encryptor);
3109 }
3110
3111 void setup_consensus(
3112 bool public_only = false,
3113 const std::optional<ccf::crypto::Pem>& endorsed_node_certificate_ =
3114 std::nullopt)
3115 {
3116 setup_n2n_channels(endorsed_node_certificate_);
3117 setup_cmd_forwarder();
3118
3119 auto shared_state = std::make_shared<aft::State>(self);
3120
3121 auto node_client = std::make_shared<HTTPNodeClient>(
3122 rpc_map, node_sign_kp, self_signed_node_cert, endorsed_node_cert);
3123
3124 consensus = std::make_shared<RaftType>(
3125 consensus_config,
3126 std::make_unique<aft::Adaptor<ccf::kv::Store>>(network.tables),
3127 std::make_unique<::consensus::LedgerEnclave>(writer_factory),
3128 n2n_channels,
3129 shared_state,
3130 node_client,
3131 commit_callbacks,
3132 public_only);
3133
3134 network.tables->set_consensus(consensus);
3135 network.tables->set_snapshotter(snapshotter);
3136
3137 // When a node is added, even locally, inform consensus so that it
3138 // can add a new active configuration.
3139 network.tables->set_map_hook(
3140 network.nodes.get_name(),
3142 [](ccf::kv::Version version, const Nodes::Write& w)
3144 return std::make_unique<ConfigurationChangeHook>(version, w);
3145 }));
3146
3147 // Note: The Signatures hook and SerialisedMerkleTree hook are separate
3148 // because the signature and the Merkle tree are recorded in distinct
3149 // tables (for serialisation performance reasons). However here, they are
3150 // expected to always be called together and for the same version as they
3151 // are always written by each signature transaction.
3152
3153 network.tables->set_map_hook(
3154 network.cose_signatures.get_name(),
3156 [s = this->snapshotter](
3157 ccf::kv::Version version,
3159 assert(w.has_value());
3160 s->record_cose_signature(version, w.value());
3161 return {nullptr};
3162 }));
3163
3164 network.tables->set_map_hook(
3165 network.serialise_tree.get_name(),
3167 [s = this->snapshotter](
3168 ccf::kv::Version version,
3170 assert(w.has_value());
3171 const auto& tree = w.value();
3172 s->record_serialised_tree(version, tree);
3173 return {nullptr};
3174 }));
3175
3176 network.tables->set_map_hook(
3177 network.snapshot_evidence.get_name(),
3179 [s = this->snapshotter](
3180 ccf::kv::Version version,
3182 assert(w.has_value());
3183 auto snapshot_evidence = w.value();
3184 s->record_snapshot_evidence_idx(version, snapshot_evidence);
3185 return {nullptr};
3186 }));
3187
3188 network.tables->set_global_hook(
3189 network.snapshot_evidence.get_name(),
3191 [this](
3192 [[maybe_unused]] ccf::kv::Version version,
3193 const SnapshotEvidence::Write& w) {
3194 if (!w.has_value())
3195 {
3196 return;
3197 }
3198
3199 auto snapshot_evidence = w.value();
3200
3201 // If backup snapshot fetching is enabled and this node is a
3202 // backup, schedule a fetch task
3203 if (
3204 config.snapshots.backup_fetch.enabled && consensus != nullptr &&
3205 !consensus->is_primary())
3206 {
3207 std::lock_guard<pal::Mutex> guard(lock);
3208 if (
3209 backup_snapshot_fetch_task != nullptr &&
3210 !backup_snapshot_fetch_task->is_cancelled())
3211 {
3213 "Backup snapshot fetch already in progress, skipping");
3214 }
3215 else
3216 {
3218 "Snapshot evidence detected on backup - scheduling "
3219 "snapshot fetch from primary (since seqno: {})",
3220 snapshot_evidence.version);
3221 backup_snapshot_fetch_task =
3222 std::make_shared<BackupSnapshotFetch>(
3223 config.snapshots, snapshot_evidence.version, this);
3224 ccf::tasks::add_task(backup_snapshot_fetch_task);
3225 }
3226 }
3227 }));
3228
3229 // Keep the globally committed snapshot baseline in sync between primary
3230 // and backups for bounded snapshotting.
3231 network.tables->set_global_hook(
3232 Tables::SNAPSHOT_STATUS,
3234 [s = this->snapshotter](
3236 assert(w.has_value());
3237 s->record_snapshot_status(w.value());
3238 }));
3239
3240 if (signature_cache != nullptr)
3241 {
3242 signature_cache->register_hooks(*network.tables);
3243 }
3244
3245 setup_basic_hooks();
3246 }
3247
3248 void setup_snapshotter()
3249 {
3250 if (snapshotter)
3251 {
3252 throw std::logic_error("Snapshotter already initialised");
3253 }
3254
3255 if (
3256 config.snapshots.min_tx_count < 2 &&
3257 std::chrono::microseconds(config.snapshots.time_interval).count() > 0)
3258 {
3260 "snapshots.min_tx_count is lower than 2 while "
3261 "snapshots.time_interval is set to {}. Time-based snapshots may "
3262 "continue to be generated without application writes due to the "
3263 "writes to snapshot evidence and its signature.",
3264 config.snapshots.time_interval.str);
3265 }
3266
3267 snapshotter = std::make_shared<Snapshotter>(
3268 writer_factory,
3269 network.tables,
3270 config.snapshots.tx_count,
3271 config.snapshots.min_tx_count,
3272 config.snapshots.time_interval);
3273 }
3274
3275 void read_ledger_entries(::consensus::Index from, ::consensus::Index to)
3276 {
3278 ::consensus::ledger_get_range,
3279 to_host,
3280 from,
3281 to,
3283 }
3284
3285 void ledger_truncate(::consensus::Index idx, bool recovery_mode = false)
3286 {
3288 ::consensus::ledger_truncate, to_host, idx, recovery_mode);
3289 }
3290
3291 public:
3292 void set_n2n_message_limit(size_t message_limit)
3293 {
3294 n2n_channels->set_message_limit(message_limit);
3295 }
3296
3297 void set_n2n_idle_timeout(std::chrono::milliseconds idle_timeout)
3298 {
3299 n2n_channels->set_idle_timeout(idle_timeout);
3300 }
3301
3302 [[nodiscard]] const ccf::StartupConfig& get_node_config() const override
3303 {
3304 return config;
3305 }
3306
3308 {
3309 return network.identity->cert;
3310 }
3311
3312 // Stop-gap until it becomes easier to use other HTTP clients
3314 const ::http::URL& url,
3315 ::http::Request&& req,
3316 std::function<bool(
3317 ccf::http_status status, http::HeaderMap&&, std::vector<uint8_t>&&)>
3318 callback,
3319 const std::vector<std::string>& ca_certs = {},
3320 const std::string& app_protocol = "HTTP1",
3321 bool authenticate_as_node_client_certificate = false) override
3322 {
3323 std::optional<ccf::crypto::Pem> client_cert = std::nullopt;
3324 std::optional<ccf::crypto::Pem> client_cert_key = std::nullopt;
3325 if (authenticate_as_node_client_certificate)
3326 {
3327 client_cert =
3328 endorsed_node_cert ? *endorsed_node_cert : self_signed_node_cert;
3329 client_cert_key = node_sign_kp->private_key_pem();
3330 }
3331
3332 auto ca = std::make_shared<::tls::CA>(ca_certs, true);
3333 std::shared_ptr<::tls::Cert> ca_cert =
3334 std::make_shared<::tls::Cert>(ca, client_cert, client_cert_key);
3335 auto client = rpcsessions->create_client(ca_cert, app_protocol);
3336 client->connect(
3337 url.host,
3338 url.port,
3339 [callback](
3340 ccf::http_status status,
3341 http::HeaderMap&& headers,
3342 std::vector<uint8_t>&& data) {
3343 return callback(status, std::move(headers), std::move(data));
3344 });
3345 client->send_request(std::move(req));
3346 }
3347
3348 void write_snapshot(std::span<uint8_t> snapshot_buf, size_t request_id)
3349 {
3350 snapshotter->write_snapshot(snapshot_buf, request_id);
3351 }
3352
3353 std::shared_ptr<ccf::kv::Store> get_store() override
3354 {
3355 return network.tables;
3356 }
3357
3359 {
3360 return writer_factory;
3361 }
3362
3364 {
3365 return recovery_decision_protocol;
3366 }
3367
3369 {
3370 if (!is_part_of_network())
3371 {
3373 "Skipping shuffling of sealed shares during recovery as ledger "
3374 "secrets are not yet available");
3375 return;
3376 }
3377 auto latest_ledger_secret = network.ledger_secrets->get_latest(tx);
3378 sealing::shuffle_sealed_shares(tx, latest_ledger_secret.second);
3379 }
3380 };
3381}
#define CCF_ASSERT_FMT(expr,...)
Definition ccf_assert.h:10
Definition raft_types.h:41
Definition raft.h:98
Definition node_interface.h:23
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 QuoteVerificationResult verify_quote_against_store(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=nullptr)
Definition quote.cpp:585
static std::optional< pal::PlatformAttestationMeasurement > get_measurement(const QuoteInfo &quote_info)
Definition quote.cpp:135
Definition history.h:563
void set_node_id(const NodeId &id_)
Definition history.h:714
Definition node_state.h:138
void initiate_private_recovery_unsafe(ccf::kv::Tx &tx, LedgerSecretsMap recovered_ledger_secrets)
Definition node_state.h:2188
void write_snapshot(std::span< uint8_t > snapshot_buf, size_t request_id)
Definition node_state.h:3348
void start_ledger_recovery_unsafe()
Definition node_state.h:1435
bool rekey_ledger(ccf::kv::Tx &tx) override
Definition node_state.h:2373
void initiate_private_recovery_unsealing_unsafe(ccf::kv::Tx &tx, const LedgerSecretPtr &unsealed_ledger_secret)
Definition node_state.h:2175
bool is_part_of_public_network() const override
Definition node_state.h:2348
NodeState(ringbuffer::AbstractWriterFactory &writer_factory, NetworkState &network, std::shared_ptr< RPCSessions > rpcsessions, ccf::crypto::CurveID curve_id_)
Definition node_state.h:610
void setup_one_off_secret_hook()
Definition node_state.h:1910
void tick_end()
Definition node_state.h:2223
void recover_private_ledger_entries(const std::vector< uint8_t > &entries)
Definition node_state.h:1735
void recv_node_inbound(const uint8_t *data, size_t size)
Definition node_state.h:2247
RecoveryDecisionProtocolSubsystem & get_recovery_decision_protocol() override
Definition node_state.h:3363
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) override
Definition node_state.h:628
ccf::crypto::Pem get_network_cert() override
Definition node_state.h:3307
bool is_reading_private_ledger() const override
Definition node_state.h:2343
std::optional< ccf::NodeId > get_primary() override
Definition node_state.h:2323
void trigger_ledger_chunk(ccf::kv::Tx &tx) override
Definition node_state.h:2013
bool is_primary() const override
Definition node_state.h:2305
void recover_public_ledger_end_unsafe()
Definition node_state.h:1557
SessionMetrics get_session_metrics() override
Definition node_state.h:2418
std::shared_ptr< ccf::kv::Store > get_store() override
Definition node_state.h:3353
void initiate_join()
Definition node_state.h:1375
friend class RecoveryDecisionProtocolSubsystem
Definition node_state.h:139
void advance_part_of_network()
Definition node_state.h:1546
void advance_part_of_public_network()
Definition node_state.h:1538
ccf::kv::Version get_startup_snapshot_seqno() override
Definition node_state.h:2412
const ccf::COSESignaturesConfig & get_cose_signatures_config() override
Definition node_state.h:2429
ringbuffer::AbstractWriterFactory & get_writer_factory() override
Definition node_state.h:3358
void auto_refresh_jwt_keys()
Definition node_state.h:1399
void recover_ledger_end()
Definition node_state.h:1943
void initiate_join_unsafe()
Definition node_state.h:1044
void stop_notice() override
Definition node_state.h:2236
void set_n2n_message_limit(size_t message_limit)
Definition node_state.h:3292
void make_http_request(const ::http::URL &url, ::http::Request &&req, std::function< bool(ccf::http_status status, http::HeaderMap &&, std::vector< uint8_t > &&)> callback, const std::vector< std::string > &ca_certs={}, const std::string &app_protocol="HTTP1", bool authenticate_as_node_client_certificate=false) override
Definition node_state.h:3313
bool can_replicate() override
Definition node_state.h:2314
void transition_service_to_open(ccf::kv::Tx &tx, AbstractGovernanceEffects::ServiceIdentities identities) override
Definition node_state.h:2035
bool is_reading_public_ledger() const override
Definition node_state.h:2338
void launch_node()
Definition node_state.h:689
void initiate_quote_generation()
Definition node_state.h:813
void initialize(const ccf::consensus::Configuration &consensus_config_, std::shared_ptr< RPCMap > rpc_map_, std::shared_ptr< AbstractRPCResponder > rpc_sessions_, std::shared_ptr< indexing::Indexer > indexer_, std::shared_ptr< ccf::CommitCallbackSubsystem > commit_callbacks_, std::shared_ptr< ccf::SignatureCacheSubsystem > signature_cache_, size_t sig_tx_interval_, size_t sig_ms_interval_)
Definition node_state.h:649
void initiate_private_recovery(ccf::kv::Tx &tx) override
Definition node_state.h:2165
void setup_private_recovery_store()
Definition node_state.h:1966
const ccf::StartupConfig & get_node_config() const override
Definition node_state.h:3302
void trigger_snapshot(ccf::kv::Tx &tx) override
Definition node_state.h:2024
bool is_in_initialised_state() const override
Definition node_state.h:2328
NodeCreateInfo create(StartType start_type_, const ccf::StartupConfig &config_)
Definition node_state.h:954
bool has_received_stop_notice() override
Definition node_state.h:2242
bool is_part_of_network() const override
Definition node_state.h:2333
void start_join_timer()
Definition node_state.h:1381
ExtendedState state() override
Definition node_state.h:2361
void trigger_recovery_shares_refresh(ccf::kv::Tx &tx) override
Definition node_state.h:2008
void set_n2n_idle_timeout(std::chrono::milliseconds idle_timeout)
Definition node_state.h:3297
NodeId get_node_id() const
Definition node_state.h:2407
void tick(std::chrono::milliseconds elapsed)
Definition node_state.h:2202
void recover_public_ledger_entries(const std::vector< uint8_t > &entries)
Definition node_state.h:1450
void recover_private_ledger_end_unsafe()
Definition node_state.h:1807
void shuffle_sealed_shares(ccf::kv::Tx &tx) override
Definition node_state.h:3368
ccf::crypto::Pem get_self_signed_certificate() override
Definition node_state.h:2423
size_t get_jwt_attempts() override
Definition node_state.h:1427
bool is_accessible_to_members() const override
Definition node_state.h:2353
Definition recovery_decision_protocol.h:44
Definition ec_key_pair.h:16
Definition pem.h:18
const std::string & str() const
Definition pem.h:46
std::vector< uint8_t > raw() const
Definition pem.h:71
Definition sha256_hash.h:16
Definition committable_tx.h:19
Definition tx.h:159
M::ReadOnlyHandle * ro(M &m)
Definition tx.h:168
Definition tx.h:200
M::Handle * rw(M &m)
Definition tx.h:211
M::WriteOnlyHandle * wo(M &m)
Definition tx.h:232
Definition map.h:30
std::map< K, std::optional< V > > Write
Definition map.h:40
static ccf::kv::untyped::CommitHook wrap_commit_hook(const CommitHook &hook)
Definition map.h:73
static ccf::kv::untyped::MapHook wrap_map_hook(const MapHook &hook)
Definition map.h:80
Definition value.h:32
static ccf::kv::untyped::CommitHook wrap_commit_hook(const CommitHook &hook)
Definition value.h:65
static ccf::kv::untyped::MapHook wrap_map_hook(const MapHook &hook)
Definition value.h:72
std::optional< V > Write
Definition value.h:38
static std::vector< uint8_t > get_entry(const uint8_t *&data, size_t &size)
Definition ledger_enclave.h:26
Definition state_machine.h:15
void expect(T state_) const
Definition state_machine.h:25
T value() const
Definition state_machine.h:40
void advance(T state_)
Definition state_machine.h:45
bool check(T state_) const
Definition state_machine.h:35
Definition http_builder.h:117
Definition ring_buffer_types.h:157
StartType
Definition enclave_interface_types.h:92
@ Join
Definition enclave_interface_types.h:94
@ Recover
Definition enclave_interface_types.h:95
@ Start
Definition enclave_interface_types.h:93
#define LOG_INFO_FMT
Definition internal_logger.h:15
#define LOG_TRACE_FMT
Definition internal_logger.h:13
#define LOG_DEBUG_FMT
Definition internal_logger.h:14
#define LOG_FAIL_FMT
Definition internal_logger.h:16
CurveID
Definition curve.h:18
std::vector< uint8_t > raw_from_b64(const std::string_view &b64_string)
Definition base64.cpp:12
ccf::crypto::Pem public_key_pem_from_cert(const std::vector< uint8_t > &der)
Definition verifier.cpp:48
std::string b64url_from_raw(const uint8_t *data, size_t size, bool with_padding=true)
Definition base64.cpp:51
ECKeyPairPtr make_ec_key_pair(CurveID curve_id=service_identity_curve_choice)
Definition ec_key_pair.cpp:530
VerifierPtr make_verifier(const std::vector< uint8_t > &cert)
Definition verifier.cpp:18
std::string get_subject_name(const Pem &cert)
Definition verifier.cpp:53
std::vector< uint8_t > cert_pem_to_der(const Pem &pem)
Definition verifier.cpp:38
std::map< std::string, std::string, std::less<> > HeaderMap
Definition http_header_map.h:10
if(argc !=1)
Definition kv_helpers.h:73
void register_class_ids()
Definition global_class_ids.cpp:17
std::map< ccf::kv::serialisers::SerialisedEntry, std::optional< ccf::kv::serialisers::SerialisedEntry > > Write
Definition untyped.h:16
uint64_t Term
Definition kv_types.h:46
std::unique_ptr< ConsensusHook > ConsensusHookPtr
Definition hooks.h:21
@ SUCCESS
Definition kv_types.h:213
uint64_t Version
Definition version.h:10
ApplyResult
Definition kv_types.h:305
@ PASS_SIGNATURE
Definition kv_types.h:307
@ FAIL
Definition kv_types.h:314
std::vector< ConsensusHookPtr > ConsensusHookPtrs
Definition hooks.h:22
UVMEndorsements verify_uvm_endorsements_descriptor(const std::vector< uint8_t > &uvm_endorsements_raw, const pal::PlatformAttestationMeasurement &uvm_measurement)
Definition uvm_endorsements.cpp:391
std::mutex Mutex
Definition locking.h:12
SealedRecoveryKey get_snp_sealed_recovery_key(const pal::snp::TcbVersionRaw &tcb_version)
Definition local_sealing.cpp:88
Task make_basic_task(Ts &&... ts)
Definition basic_task.h:33
void add_periodic_task(Task task, std::chrono::milliseconds initial_delay, std::chrono::milliseconds repeat_period)
Definition task_system.cpp:75
void add_task(Task task)
Definition task_system.cpp:65
std::shared_ptr< BaseTask > Task
Definition task.h:36
Definition app_interface.h:13
EntityId< NodeIdFormatter > NodeId
Definition entity_id.h:164
NodeId compute_node_id_from_kp(const ccf::crypto::ECKeyPairPtr &node_sign_kp)
Definition nodes.h:43
constexpr auto get_actor_prefix(ActorsType at)
Definition actors.h:24
ServiceValue< SnapshotStatus > SnapshotStatusValue
Definition snapshot_status.h:23
std::vector< uint8_t > CoseSignature
Definition signatures.h:64
std::tuple< NodeStartupState, std::optional< ccf::kv::Version >, std::optional< ccf::kv::Version > > ExtendedState
Definition node_operation_interface.h:21
std::list< EncryptedLedgerSecretInfo > RecoveredEncryptedLedgerSecrets
Definition share_manager.h:159
std::map< ccf::kv::Version, LedgerSecretPtr > LedgerSecretsMap
Definition ledger_secrets.h:21
void reset_data(std::vector< uint8_t > &data)
Definition node_state.h:89
LedgerSignMode get_ledger_sign_mode()
Definition get_ledger_sign_mode_cose.cpp:8
LedgerSecretPtr make_ledger_secret()
Definition ledger_secret.h:86
NodeStartupState
Definition node_startup_state.h:10
ccf::kv::Term resolve_latest_sig_view(ccf::kv::ReadOnlyTx &tx)
Definition node_state.h:99
llhttp_status http_status
Definition http_status.h:9
QuoteVerificationResult
Definition quote.h:19
std::shared_ptr< LedgerSecret > LedgerSecretPtr
Definition ledger_secret.h:84
constexpr View VIEW_UNKNOWN
Definition tx_id.h:26
uint64_t View
Definition tx_id.h:23
@ channel_msg
Definition node_types.h:22
@ consensus_msg
Definition node_types.h:23
@ forwarded_msg
Definition node_types.h:24
void to_json(nlohmann::json &j, const ClaimsDigest &hash)
Definition claims_digest.h:49
std::shared_ptr<::http::HttpRpcContext > make_rpc_context(std::shared_ptr< ccf::SessionContext > s, const std::vector< uint8_t > &packed)
Definition http_rpc_context.h:364
Definition perf_client.h:12
Definition consensus_types.h:23
uint64_t Index
Definition ledger_enclave_types.h:11
@ Recovery
Definition ledger_enclave_types.h:15
Definition configuration.h:14
URL parse_url_full(const std::string &url)
Definition http_parser.h:151
std::shared_ptr< AbstractWriter > WriterPtr
Definition ring_buffer_types.h:154
std::vector< std::pair< size_t, fs::path > > find_committed_snapshots_in_directories(const std::vector< fs::path > &directories, std::optional< size_t > minimum_idx=std::nullopt)
Definition filenames.h:167
STL namespace.
#define RINGBUFFER_WRITE_MESSAGE(MSG,...)
Definition ring_buffer_types.h:259
Definition gov_effects_interface.h:22
std::optional< ccf::crypto::Pem > previous
Definition gov_effects_interface.h:23
ccf::crypto::Pem next
Definition gov_effects_interface.h:24
std::optional< std::string > security_policy
Definition startup_config.h:84
std::optional< std::string > uvm_endorsements
Definition startup_config.h:85
std::optional< std::string > snp_endorsements
Definition startup_config.h:86
ccf::pal::snp::EndorsementsServers snp_endorsements_servers
Definition startup_config.h:75
std::optional< std::string > snp_endorsements_file
Definition startup_config.h:78
Environment environment
Definition startup_config.h:90
ccf::ds::TimeString key_refresh_interval
Definition startup_config.h:67
std::string subject_name
Definition startup_config.h:37
std::vector< std::string > subject_alt_names
Definition startup_config.h:38
size_t initial_validity_days
Definition startup_config.h:40
std::string target_rpc_interface
Definition startup_config.h:109
bool enabled
Definition startup_config.h:106
Definition startup_config.h:97
ccf::ds::TimeString time_interval
Definition startup_config.h:101
std::string directory
Definition startup_config.h:98
std::optional< std::string > read_only_directory
Definition startup_config.h:102
BackupFetch backup_fetch
Definition startup_config.h:114
size_t min_tx_count
Definition startup_config.h:100
size_t tx_count
Definition startup_config.h:99
ccf::NodeInfoNetwork network
Definition startup_config.h:33
Snapshots snapshots
Definition startup_config.h:118
JWT jwt
Definition startup_config.h:71
Attestation attestation
Definition startup_config.h:94
NodeCertificateInfo node_certificate
Definition startup_config.h:44
Definition cose_signatures_config.h:12
Definition node_call_types.h:106
std::optional< NetworkInfo > network_info
Definition node_call_types.h:161
NodeStatus node_status
Definition node_call_types.h:107
Definition network_state.h:12
Definition node_state.h:84
ccf::crypto::Pem service_cert
Definition node_state.h:86
ccf::crypto::Pem self_signed_node_cert
Definition node_state.h:85
Definition odata_error.h:50
Describes a quote (attestation) from trusted hardware.
Definition quote_info.h:26
Definition session_metrics.h:13
Definition startup_config.h:177
std::vector< uint8_t > service_cert
Definition startup_config.h:180
bool follow_redirect
Definition startup_config.h:181
ccf::NodeInfoNetwork::NetAddress target_rpc_address
Definition startup_config.h:178
bool fetch_recent_snapshot
Definition startup_config.h:182
std::optional< std::string > host_data_transparent_statement_path
Definition startup_config.h:186
ccf::ds::TimeString fetch_snapshot_retry_interval
Definition startup_config.h:184
ccf::ds::TimeString retry_timeout
Definition startup_config.h:179
ccf::ds::SizeString fetch_snapshot_max_size
Definition startup_config.h:185
size_t fetch_snapshot_max_attempts
Definition startup_config.h:183
std::optional< std::vector< uint8_t > > previous_service_identity
Definition startup_config.h:193
Definition startup_config.h:148
size_t initial_service_certificate_validity_days
Definition startup_config.h:156
ccf::COSESignaturesConfig cose_signatures
Definition startup_config.h:158
nlohmann::json service_data
Definition startup_config.h:162
Start start
Definition startup_config.h:174
Join join
Definition startup_config.h:189
nlohmann::json node_data
Definition startup_config.h:164
Recover recover
Definition startup_config.h:196
std::string startup_host_time
Definition startup_config.h:152
std::string service_subject_name
Definition startup_config.h:157
std::optional< SealingRecoveryConfig > sealing_recovery
Definition startup_config.h:160
static std::optional< TxID > from_str(const std::string_view &sv)
Definition tx_id.h:53
Definition consensus_config.h:11
Definition cose_common.h:43
size_t count_bytes() const
Definition unit_strings.h:141
size_t count_s() const
Definition unit_strings.h:197
size_t count_ms() const
Definition unit_strings.h:192
std::string str
Definition unit_strings.h:108
Definition measurement.h:123
Definition report_data.h:56
Definition attestation_sev_snp_endorsements.h:20
Definition attestation_sev_snp.h:387
Definition task.h:15