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/logger.h"
10#include "ccf/js/core/context.h"
11#include "ccf/pal/attestation.h"
12#include "ccf/pal/locking.h"
13#include "ccf/pal/platform.h"
17#include "ccf_acme_client.h"
18#include "consensus/aft/raft.h"
20#include "crypto/certs.h"
21#include "ds/state_machine.h"
24#include "encryptor.h"
25#include "history.h"
26#include "http/http_parser.h"
27#include "indexing/indexer.h"
28#include "js/global_class_ids.h"
29#include "network_state.h"
30#include "node/hooks.h"
34#include "node/snapshotter.h"
35#include "node_to_node.h"
37#include "rpc/frontend.h"
38#include "rpc/serialization.h"
39#include "secret_broadcast.h"
41#include "share_manager.h"
42#include "uvm_endorsements.h"
43
44#ifdef USE_NULL_ENCRYPTOR
45# include "kv/test/null_encryptor.h"
46#endif
47
48#include <atomic>
49#include <chrono>
50#define FMT_HEADER_ONLY
51#include <fmt/format.h>
52#include <nlohmann/json.hpp>
53#include <stdexcept>
54#include <unordered_set>
55#include <vector>
56
57namespace ccf
58{
60
66
67 void reset_data(std::vector<uint8_t>& data)
68 {
69 data.clear();
70 data.shrink_to_fit();
71 }
72
74 {
75 private:
76 //
77 // this node's core state
78 //
80 pal::Mutex lock;
81 StartType start_type;
82
83 ccf::crypto::CurveID curve_id;
84 std::vector<ccf::crypto::SubjectAltName> subject_alt_names = {};
85
86 std::shared_ptr<ccf::crypto::KeyPair_OpenSSL> node_sign_kp;
87 NodeId self;
88 std::shared_ptr<ccf::crypto::RSAKeyPair> node_encrypt_kp;
89 ccf::crypto::Pem self_signed_node_cert;
90 std::optional<ccf::crypto::Pem> endorsed_node_cert = std::nullopt;
91 QuoteInfo quote_info;
93 StartupConfig config;
94 std::optional<UVMEndorsements> snp_uvm_endorsements = std::nullopt;
95 std::vector<uint8_t> startup_snapshot;
96 std::shared_ptr<QuoteEndorsementsClient> quote_endorsements_client =
97 nullptr;
98
99 std::atomic<bool> stop_noticed = false;
100
101 struct NodeStateMsg
102 {
103 NodeStateMsg(
104 NodeState& self_,
105 View create_view_ = 0,
106 bool create_consortium_ = true) :
107 self(self_),
108 create_view(create_view_),
109 create_consortium(create_consortium_)
110 {}
111 NodeState& self;
112 View create_view;
113 bool create_consortium;
114 };
115
116 //
117 // kv store, replication, and I/O
118 //
119 ringbuffer::AbstractWriterFactory& writer_factory;
120 ringbuffer::WriterPtr to_host;
121 ccf::consensus::Configuration consensus_config;
122 size_t sig_tx_interval;
123 size_t sig_ms_interval;
124
125 NetworkState& network;
126
127 std::shared_ptr<ccf::kv::Consensus> consensus;
128 std::shared_ptr<RPCMap> rpc_map;
129 std::shared_ptr<indexing::Indexer> indexer;
130 std::shared_ptr<NodeToNode> n2n_channels;
131 std::shared_ptr<Forwarder<NodeToNode>> cmd_forwarder;
132 std::shared_ptr<RPCSessions> rpcsessions;
133
134 std::shared_ptr<ccf::kv::TxHistory> history;
135 std::shared_ptr<ccf::kv::AbstractTxEncryptor> encryptor;
136
137 ShareManager share_manager;
138 std::shared_ptr<Snapshotter> snapshotter;
139
140 //
141 // recovery
142 //
143 std::shared_ptr<ccf::kv::Store> recovery_store;
144
145 ccf::kv::Version recovery_v;
146 ccf::crypto::Sha256Hash recovery_root;
147 std::vector<ccf::kv::Version> view_history;
148 ::consensus::Index last_recovered_signed_idx = 0;
149 RecoveredEncryptedLedgerSecrets recovered_encrypted_ledger_secrets = {};
150 ::consensus::Index last_recovered_idx = 0;
151 static const size_t recovery_batch_size = 100;
152
153 //
154 // JWT key auto-refresh
155 //
156 std::shared_ptr<JwtKeyAutoRefresh> jwt_key_auto_refresh;
157
158 std::unique_ptr<StartupSnapshotInfo> startup_snapshot_info = nullptr;
159 // Set to the snapshot seqno when a node starts from one and remembered for
160 // the lifetime of the node
161 ccf::kv::Version startup_seqno = 0;
162
163 // ACME certificate endorsement client
164 std::map<NodeInfoNetwork::RpcInterfaceID, std::shared_ptr<ACMEClient>>
165 acme_clients;
166 std::map<
168 std::shared_ptr<ACMEChallengeHandler>>
169 acme_challenge_handlers;
170 size_t num_acme_interfaces = 0;
171
172 std::shared_ptr<ccf::kv::AbstractTxEncryptor> make_encryptor()
173 {
174#ifdef USE_NULL_ENCRYPTOR
175 return std::make_shared<ccf::kv::NullTxEncryptor>();
176#else
177 return std::make_shared<NodeEncryptor>(network.ledger_secrets);
178#endif
179 }
180
181 // Returns true if the snapshot is already verified (via embedded receipt)
182 void initialise_startup_snapshot(bool recovery = false)
183 {
184 std::shared_ptr<ccf::kv::Store> snapshot_store;
185 if (!recovery)
186 {
187 // Create a new store to verify the snapshot only
188 snapshot_store = make_store();
189 auto snapshot_history = std::make_shared<MerkleTxHistory>(
190 *snapshot_store.get(),
191 self,
192 *node_sign_kp,
193 sig_tx_interval,
194 sig_ms_interval,
195 false /* No signature timer on snapshot_history */);
196
197 auto snapshot_encryptor = make_encryptor();
198
199 snapshot_store->set_history(snapshot_history);
200 snapshot_store->set_encryptor(snapshot_encryptor);
201 }
202 else
203 {
204 snapshot_store = network.tables;
205 }
206
208 startup_snapshot_info = initialise_from_snapshot(
209 snapshot_store,
210 std::move(startup_snapshot),
211 hooks,
212 &view_history,
213 true,
215
216 startup_seqno = startup_snapshot_info->seqno;
217 last_recovered_idx = startup_seqno;
218 last_recovered_signed_idx = last_recovered_idx;
219 }
220
221 public:
223 ringbuffer::AbstractWriterFactory& writer_factory,
224 NetworkState& network,
225 std::shared_ptr<RPCSessions> rpcsessions,
226 ccf::crypto::CurveID curve_id_) :
227 sm("NodeState", NodeStartupState::uninitialized),
228 curve_id(curve_id_),
229 node_sign_kp(std::make_shared<ccf::crypto::KeyPair_OpenSSL>(curve_id_)),
230 self(compute_node_id_from_kp(node_sign_kp)),
231 node_encrypt_kp(ccf::crypto::make_rsa_key_pair()),
232 writer_factory(writer_factory),
233 to_host(writer_factory.create_writer_to_outside()),
234 network(network),
235 rpcsessions(rpcsessions),
236 share_manager(network.ledger_secrets)
237 {}
238
241 const QuoteInfo& quote_info_,
242 const std::vector<uint8_t>& expected_node_public_key_der,
243 pal::PlatformAttestationMeasurement& measurement) override
244 {
246 tx, quote_info_, expected_node_public_key_der, measurement);
247 }
248
249 //
250 // funcs in state "uninitialized"
251 //
253 const ccf::consensus::Configuration& consensus_config_,
254 std::shared_ptr<RPCMap> rpc_map_,
255 std::shared_ptr<AbstractRPCResponder> rpc_sessions_,
256 std::shared_ptr<indexing::Indexer> indexer_,
257 size_t sig_tx_interval_,
258 size_t sig_ms_interval_)
259 {
260 std::lock_guard<pal::Mutex> guard(lock);
262
263 consensus_config = consensus_config_;
264 rpc_map = rpc_map_;
265 indexer = indexer_;
266 sig_tx_interval = sig_tx_interval_;
267 sig_ms_interval = sig_ms_interval_;
268
269 n2n_channels = std::make_shared<NodeToNodeChannelManager>(writer_factory);
270
271 cmd_forwarder = std::make_shared<Forwarder<NodeToNode>>(
272 rpc_sessions_, n2n_channels, rpc_map);
273
275
276 for (auto& [actor, fe] : rpc_map->frontends())
277 {
278 fe->set_sig_intervals(sig_tx_interval, sig_ms_interval);
279 fe->set_cmd_forwarder(cmd_forwarder);
280 }
281 }
282
283 //
284 // funcs in state "initialized"
285 //
287 {
288 auto measurement = AttestationProvider::get_measurement(quote_info);
289 if (measurement.has_value())
290 {
291 node_measurement = measurement.value();
292 }
293 else
294 {
295 throw std::logic_error("Failed to extract code id from quote");
296 }
297
298 // Verify that the security policy matches the quoted digest of the policy
299 if (quote_info.format == QuoteFormat::amd_sev_snp_v1)
300 {
301 if (!config.attestation.environment.security_policy.has_value())
302 {
304 "Security policy not set, skipping check against attestation host "
305 "data");
306 }
307 else
308 {
309 auto quoted_digest = AttestationProvider::get_host_data(quote_info);
310 if (!quoted_digest.has_value())
311 {
312 throw std::logic_error("Unable to find host data in attestation");
313 }
314
315 auto const& security_policy =
317
318 auto security_policy_digest =
320 if (security_policy_digest != quoted_digest.value())
321 {
322 throw std::logic_error(fmt::format(
323 "Digest of decoded security policy \"{}\" {} does not match "
324 "attestation host data {}",
325 security_policy,
326 security_policy_digest.hex_str(),
327 quoted_digest.value().hex_str()));
328 }
330 "Successfully verified attested security policy {}",
331 security_policy_digest);
332 }
333
334 if (!config.attestation.environment.uvm_endorsements.has_value())
335 {
337 "UVM endorsements not set, skipping check against attestation "
338 "measurement");
339 }
340 else
341 {
342 try
343 {
344 auto uvm_endorsements_raw = ccf::crypto::raw_from_b64(
346 snp_uvm_endorsements =
347 verify_uvm_endorsements(uvm_endorsements_raw, node_measurement);
348 quote_info.uvm_endorsements = uvm_endorsements_raw;
349 }
350 catch (const std::exception& e)
351 {
352 throw std::logic_error(
353 fmt::format("Error verifying UVM endorsements: {}", e.what()));
354 }
355 }
356 }
357
358 switch (start_type)
359 {
360 case StartType::Start:
361 {
362 LOG_INFO_FMT("Creating boot request");
363 create_and_send_boot_request(
364 aft::starting_view_change, true /* Create new consortium */);
365 return;
366 }
367 case StartType::Join:
368 {
369 if (!startup_snapshot.empty())
370 {
371 initialise_startup_snapshot();
372 }
373
376 return;
377 }
379 {
380 setup_recovery_hook();
381 if (!startup_snapshot.empty())
382 {
383 initialise_startup_snapshot(true);
384 snapshotter->set_last_snapshot_idx(last_recovered_idx);
385 }
386
389 return;
390 }
391 default:
392 {
393 throw std::logic_error(
394 fmt::format("Node was launched in unknown mode {}", start_type));
395 }
396 }
397 }
398
400 {
401 auto fetch_endorsements = [this](
402 const QuoteInfo& qi,
403 const pal::snp::
404 EndorsementEndpointsConfiguration&
405 endpoint_config) {
406 // Note: Node lock is already taken here as this is called back
407 // synchronously with the call to pal::generate_quote
408
410 {
411 if (config.attestation.snp_endorsements_servers.empty())
412 {
413 throw std::runtime_error(
414 "One or more SNP endorsements servers must be specified to fetch "
415 "the collateral for the attestation");
416 }
417 // On SEV-SNP, fetch endorsements from servers if specified
418 quote_endorsements_client = std::make_shared<QuoteEndorsementsClient>(
419 rpcsessions,
420 endpoint_config,
421 [this, qi](std::vector<uint8_t>&& endorsements) {
422 std::lock_guard<pal::Mutex> guard(lock);
423 quote_info = qi;
424 quote_info.endorsements = std::move(endorsements);
425 try
426 {
427 launch_node();
428 }
429 catch (const std::exception& e)
430 {
431 LOG_FAIL_FMT("{}", e.what());
432 throw;
433 }
434 quote_endorsements_client.reset();
435 });
436
437 quote_endorsements_client->fetch_endorsements();
438 return;
439 }
440
441 if (!((qi.format == QuoteFormat::oe_sgx_v1 &&
442 !qi.endorsements.empty()) ||
443 (qi.format != QuoteFormat::oe_sgx_v1 && qi.endorsements.empty())))
444 {
445 throw std::runtime_error(
446 "SGX quote generation should have already fetched endorsements");
447 }
448
449 quote_info = qi;
450 launch_node();
451 };
452
454 ccf::crypto::Sha256Hash((node_sign_kp->public_key_der()));
455
456 pal::generate_quote(
457 report_data,
458 fetch_endorsements,
460 }
461
463 StartType start_type_,
464 StartupConfig&& config_,
465 std::vector<uint8_t>&& startup_snapshot_)
466 {
467 std::lock_guard<pal::Mutex> guard(lock);
469 start_type = start_type_;
470
471 config = std::move(config_);
472 startup_snapshot = std::move(startup_snapshot_);
473 subject_alt_names = get_subject_alternative_names();
474
476 self_signed_node_cert = create_self_signed_cert(
477 node_sign_kp,
479 subject_alt_names,
480 config.startup_host_time,
482
483 accept_node_tls_connections();
484 open_frontend(ActorsType::nodes);
485
486 // Signatures are only emitted on a timer once the public ledger has been
487 // recovered
488 setup_history();
489 setup_snapshotter();
490 setup_encryptor();
491
492 setup_acme_clients();
493
495
496 switch (start_type)
497 {
498 case StartType::Start:
499 {
500 network.identity = std::make_unique<ReplicatedNetworkIdentity>(
502 curve_id,
503 config.startup_host_time,
505
506 network.ledger_secrets->init();
507
508 setup_consensus(
511 false,
512 endorsed_node_cert);
513
514 // Become the primary and force replication
515 consensus->force_become_primary();
516
517 LOG_INFO_FMT("Created new node {}", self);
518 return {self_signed_node_cert, network.identity->cert};
519 }
520 case StartType::Join:
521 {
522 LOG_INFO_FMT("Created join node {}", self);
523 return {self_signed_node_cert, {}};
524 }
526 {
528 {
529 throw std::logic_error(
530 "Recovery requires the certificate of the previous service "
531 "identity");
532 }
533
534 ccf::crypto::Pem previous_service_identity_cert(
535 config.recover.previous_service_identity.value());
536
537 network.identity = std::make_unique<ReplicatedNetworkIdentity>(
538 ccf::crypto::get_subject_name(previous_service_identity_cert),
539 curve_id,
540 config.startup_host_time,
542
543 LOG_INFO_FMT("Created recovery node {}", self);
544 return {self_signed_node_cert, network.identity->cert};
545 }
546 default:
547 {
548 throw std::logic_error(
549 fmt::format("Node was started in unknown mode {}", start_type));
550 }
551 }
552 }
553
554 //
555 // funcs in state "pending"
556 //
557
559 {
561
562 auto network_ca = std::make_shared<::tls::CA>(std::string(
563 config.join.service_cert.begin(), config.join.service_cert.end()));
564
565 auto join_client_cert = std::make_unique<::tls::Cert>(
566 network_ca,
567 self_signed_node_cert,
568 node_sign_kp->private_key_pem(),
569 config.join.target_rpc_address);
570
571 // Create RPC client and connect to remote node
572 // Note: For now, assume that target node accepts same application
573 // protocol as this node's main RPC interface
574 auto join_client = rpcsessions->create_client(
575 std::move(join_client_cert),
576 rpcsessions->get_app_protocol_main_interface());
577
578 auto [target_host, target_port] =
579 split_net_address(config.join.target_rpc_address);
580
581 join_client->connect(
582 target_host,
583 target_port,
584 [this](
585 http_status status,
586 http::HeaderMap&& headers,
587 std::vector<uint8_t>&& data) {
588 std::lock_guard<pal::Mutex> guard(lock);
590 {
591 return;
592 }
593
594 if (status != HTTP_STATUS_OK)
595 {
596 const auto& location = headers.find(http::headers::LOCATION);
597 if (
598 config.join.follow_redirect &&
599 (status == HTTP_STATUS_PERMANENT_REDIRECT ||
600 status == HTTP_STATUS_TEMPORARY_REDIRECT) &&
601 location != headers.end())
602 {
603 const auto& url = ::http::parse_url_full(location->second);
604 config.join.target_rpc_address =
605 make_net_address(url.host, url.port);
606 LOG_INFO_FMT("Target node redirected to {}", location->second);
607 }
608 else
609 {
611 "An error occurred while joining the network: {} {}{}",
612 status,
613 http_status_str(status),
614 data.empty() ?
615 "" :
616 fmt::format(" '{}'", std::string(data.begin(), data.end())));
617 }
618 return;
619 }
620
621 auto j = nlohmann::json::parse(data);
622
624 try
625 {
626 resp = j.get<JoinNetworkNodeToNode::Out>();
627 }
628 catch (const std::exception& e)
629 {
631 "An error occurred while parsing the join network response");
633 "An error occurred while parsing the join network response: {}",
634 j.dump());
635 return;
636 }
637
638 // Set network secrets, node id and become part of network.
640 {
641 network.identity = std::make_unique<ReplicatedNetworkIdentity>(
642 resp.network_info->identity);
643 network.ledger_secrets->init_from_map(
644 std::move(resp.network_info->ledger_secrets));
645
646 ccf::crypto::Pem n2n_channels_cert;
647 if (!resp.network_info->endorsed_certificate.has_value())
648 {
649 // Endorsed certificate was added to join response in 2.x
650 throw std::logic_error(
651 "Expected endorsed certificate in join response");
652 }
653 n2n_channels_cert = resp.network_info->endorsed_certificate.value();
654
655 setup_consensus(
656 resp.network_info->service_status.value_or(
659 resp.network_info->public_only,
660 n2n_channels_cert);
662
663 if (resp.network_info->public_only)
664 {
665 last_recovered_signed_idx =
666 resp.network_info->last_recovered_signed_idx;
667 setup_recovery_hook();
668 snapshotter->set_snapshot_generation(false);
669 }
670
672 std::vector<ccf::kv::Version> view_history_ = {};
673 if (startup_snapshot_info)
674 {
675 // It is only possible to deserialise the entire snapshot then,
676 // once the ledger secrets have been passed in by the network
678 deserialise_snapshot(
679 network.tables,
680 startup_snapshot_info->raw,
681 hooks,
682 &view_history_,
683 resp.network_info->public_only,
685
686 for (auto& hook : hooks)
687 {
688 hook->call(consensus.get());
689 }
690
691 auto tx = network.tables->create_read_only_tx();
692 auto signatures = tx.ro(network.signatures);
693 auto sig = signatures->get();
694 if (!sig.has_value())
695 {
696 throw std::logic_error(
697 fmt::format("No signatures found after applying snapshot"));
698 }
699 view = sig->view;
700
701 if (!resp.network_info->public_only)
702 {
703 // Only clear snapshot if not recovering. When joining the
704 // public network the snapshot is used later to initialise the
705 // recovery store
706 startup_snapshot_info.reset();
707 }
708
710 "Joiner successfully resumed from snapshot at seqno {} and "
711 "view {}",
712 network.tables->current_version(),
713 view);
714 }
715
716 consensus->init_as_backup(
717 network.tables->current_version(),
718 view,
719 view_history_,
720 last_recovered_signed_idx);
721
722 snapshotter->set_last_snapshot_idx(
723 network.tables->current_version());
724 history->start_signature_emit_timer();
725
726 if (resp.network_info->public_only)
727 {
729 }
730 else
731 {
732 reset_data(quote_info.quote);
733 reset_data(quote_info.endorsements);
735 }
736
738 "Node has now joined the network as node {}: {}",
739 self,
740 (resp.network_info->public_only ? "public only" : "all domains"));
741 }
742 else if (resp.node_status == NodeStatus::PENDING)
743 {
745 "Node {} is waiting for votes of members to be trusted", self);
746 }
747 },
748 [this](const std::string& error_msg) {
749 std::lock_guard<pal::Mutex> guard(lock);
750 auto long_error_msg = fmt::format(
751 "Early error when joining existing network at {}: {}. Shutting "
752 "down node gracefully...",
754 error_msg);
755 LOG_FAIL_FMT("{}", long_error_msg);
757 AdminMessage::fatal_error_msg, to_host, long_error_msg);
758 });
759
760 // Send RPC request to remote node to join the network.
761 JoinNetworkNodeToNode::In join_params;
762
763 join_params.node_info_network = config.network;
764 join_params.public_encryption_key = node_encrypt_kp->public_key_pem();
765 join_params.quote_info = quote_info;
766 join_params.startup_seqno = startup_seqno;
767 join_params.certificate_signing_request = node_sign_kp->create_csr(
768 config.node_certificate.subject_name, subject_alt_names);
769 join_params.node_data = config.node_data;
771
773 "Sending join request to {}", config.join.target_rpc_address);
774
775 const auto body = nlohmann::json(join_params).dump();
776
777 LOG_DEBUG_FMT("Sending join request body: {}", body);
778
780 fmt::format("/{}/{}", get_actor_prefix(ActorsType::nodes), "join"));
781 r.set_header(
782 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
783 r.set_body(body);
784
785 join_client->send_request(std::move(r));
786 }
787
789 {
790 std::lock_guard<pal::Mutex> guard(lock);
792 }
793
795 {
797
798 auto timer_msg = std::make_unique<::threading::Tmsg<NodeStateMsg>>(
799 [](std::unique_ptr<::threading::Tmsg<NodeStateMsg>> msg) {
800 std::lock_guard<pal::Mutex> guard(msg->data.self.lock);
801 if (msg->data.self.sm.check(NodeStartupState::pending))
802 {
803 msg->data.self.initiate_join_unsafe();
804 auto delay = std::chrono::milliseconds(
805 msg->data.self.config.join.retry_timeout);
806
808 std::move(msg), delay);
809 }
810 },
811 *this);
812
814 std::move(timer_msg), config.join.retry_timeout);
815 }
816
818 {
819 if (!consensus)
820 {
822 "JWT key auto-refresh: consensus not initialized, not starting "
823 "auto-refresh");
824 return;
825 }
826 jwt_key_auto_refresh = std::make_shared<JwtKeyAutoRefresh>(
828 network,
829 consensus,
830 rpcsessions,
831 rpc_map,
832 node_sign_kp,
833 self_signed_node_cert);
834 jwt_key_auto_refresh->start();
835
836 network.tables->set_map_hook(
837 network.jwt_issuers.get_name(),
840 jwt_key_auto_refresh->schedule_once();
841 return ccf::kv::ConsensusHookPtr(nullptr);
842 });
843 }
844
845 size_t get_jwt_attempts() override
846 {
847 return jwt_key_auto_refresh->get_attempts();
848 }
849
850 //
851 // funcs in state "readingPublicLedger"
852 //
854 {
856 {
857 throw std::logic_error(fmt::format(
858 "Node should be in state {} to start reading ledger",
860 }
861
862 LOG_INFO_FMT("Starting to read public ledger");
863
864 read_ledger_entries(
865 last_recovered_idx + 1, last_recovered_idx + recovery_batch_size);
866 }
867
868 void recover_public_ledger_entries(const std::vector<uint8_t>& entries)
869 {
870 std::lock_guard<pal::Mutex> guard(lock);
871
873
874 auto data = entries.data();
875 auto size = entries.size();
876
877 if (size == 0)
878 {
880 return;
881 }
882
883 while (size > 0)
884 {
885 auto entry = ::consensus::LedgerEnclave::get_entry(data, size);
886
887 LOG_INFO_FMT("Deserialising public ledger entry [{}]", entry.size());
888
889 // When reading the private ledger, deserialise in the recovery store
890
892 try
893 {
894 auto r = network.tables->deserialize(entry, true);
895 result = r->apply();
896 if (result == ccf::kv::ApplyResult::FAIL)
897 {
899 "Failed to deserialise public ledger entry: {}", result);
901 return;
902 }
903 ++last_recovered_idx;
904
905 // Not synchronised because consensus isn't effectively running then
906 for (auto& hook : r->get_hooks())
907 {
908 hook->call(consensus.get());
909 }
910 }
911 catch (const std::exception& e)
912 {
914 "Failed to deserialise public ledger entry: {}", e.what());
916 return;
917 }
918
919 // If the ledger entry is a signature, it is safe to compact the store
921 {
922 // If the ledger entry is a signature, it is safe to compact the store
923 network.tables->compact(last_recovered_idx);
924 auto tx = network.tables->create_read_only_tx();
925 auto last_sig = tx.ro(network.signatures)->get();
926
927 if (!last_sig.has_value())
928 {
929 throw std::logic_error("Signature missing");
930 }
931
933 "Read signature at {} for view {}",
934 last_recovered_idx,
935 last_sig->view);
936 // Initial transactions, before the first signature, must have
937 // happened in the first signature's view (eg - if the first
938 // signature is at seqno 20 in view 4, then transactions 1->19 must
939 // also have been in view 4). The brief justification is that while
940 // the first node may start in an arbitrarily high view (it does not
941 // necessarily start in view 1), it cannot _change_ view before a
942 // valid signature.
943 const auto view_start_idx =
944 view_history.empty() ? 1 : last_recovered_signed_idx + 1;
946 last_sig->view >= 0,
947 "last_sig->view is invalid, {}",
948 last_sig->view);
949 for (auto i = view_history.size();
950 i < static_cast<size_t>(last_sig->view);
951 ++i)
952 {
953 view_history.push_back(view_start_idx);
954 }
955 last_recovered_signed_idx = last_recovered_idx;
956 }
957 }
958
959 read_ledger_entries(
960 last_recovered_idx + 1, last_recovered_idx + recovery_batch_size);
961 }
962
964 {
965 std::lock_guard<pal::Mutex> guard(lock);
967 history->start_signature_emit_timer();
969 }
970
972 {
973 std::lock_guard<pal::Mutex> guard(lock);
975 history->start_signature_emit_timer();
977 reset_data(quote_info.quote);
978 reset_data(quote_info.endorsements);
980 }
981
983 {
985
986 // When reaching the end of the public ledger, truncate to last signed
987 // index
988 const auto last_recovered_term = view_history.size();
989 auto new_term = last_recovered_term + aft::starting_view_change;
990 LOG_INFO_FMT("Setting term on public recovery store to {}", new_term);
991
992 // Note: KV term must be set before the first Tx is committed
993 network.tables->rollback(
994 {last_recovered_term, last_recovered_signed_idx}, new_term);
995 ledger_truncate(last_recovered_signed_idx, true);
996 snapshotter->rollback(last_recovered_signed_idx);
997
999 "End of public ledger recovery - Truncating ledger to last signed "
1000 "TxID: {}.{}",
1001 last_recovered_term,
1002 last_recovered_signed_idx);
1003
1004 auto tx = network.tables->create_read_only_tx();
1005 network.ledger_secrets->init(last_recovered_signed_idx + 1);
1006
1007 // Initialise snapshotter after public recovery
1008 snapshotter->init_after_public_recovery();
1009 snapshotter->set_snapshot_generation(false);
1010
1011 ccf::kv::Version index = 0;
1012 ccf::kv::Term view = 0;
1013
1014 auto ls = tx.ro(network.signatures)->get();
1015 if (ls.has_value())
1016 {
1017 auto s = ls.value();
1018 index = s.seqno;
1019 view = s.view;
1020 }
1021
1022 auto h = dynamic_cast<MerkleTxHistory*>(history.get());
1023 if (h)
1024 {
1025 h->set_node_id(self);
1026 }
1027
1028 auto service_config = tx.ro(network.config)->get();
1029
1030 setup_consensus(
1033
1034 LOG_DEBUG_FMT("Restarting consensus at view: {} seqno: {}", view, index);
1035
1036 consensus->force_become_primary(index, view, view_history, index);
1037
1038 create_and_send_boot_request(
1039 new_term, false /* Restore consortium from ledger */);
1040 }
1041
1042 //
1043 // funcs in state "readingPrivateLedger"
1044 //
1045 void recover_private_ledger_entries(const std::vector<uint8_t>& entries)
1046 {
1047 std::lock_guard<pal::Mutex> guard(lock);
1049 {
1051 "Node in state {} cannot recover private ledger entries", sm.value());
1052 return;
1053 }
1054
1055 auto data = entries.data();
1056 auto size = entries.size();
1057
1058 if (size == 0)
1059 {
1061 return;
1062 }
1063
1064 while (size > 0)
1065 {
1066 auto entry = ::consensus::LedgerEnclave::get_entry(data, size);
1067
1069 "Deserialising private ledger entry {} [{}]",
1070 last_recovered_idx + 1,
1071 entry.size());
1072
1073 // When reading the private ledger, deserialise in the recovery store
1075 try
1076 {
1077 result = recovery_store->deserialize(entry)->apply();
1078 if (result == ccf::kv::ApplyResult::FAIL)
1079 {
1081 "Failed to deserialise private ledger entry: {}", result);
1082 // Note: rollback terms do not matter here as recovery store is
1083 // about to be discarded
1084 recovery_store->rollback({0, last_recovered_idx}, 0);
1086 return;
1087 }
1088 ++last_recovered_idx;
1089 }
1090 catch (const std::exception& e)
1091 {
1093 "Failed to deserialise private ledger entry: {}", e.what());
1095 return;
1096 }
1097
1099 {
1100 recovery_store->compact(last_recovered_idx);
1101 }
1102 }
1103
1104 if (recovery_store->current_version() == recovery_v)
1105 {
1106 LOG_INFO_FMT("Reached recovery final version at {}", recovery_v);
1108 }
1109 else
1110 {
1111 read_ledger_entries(
1112 last_recovered_idx + 1,
1113 std::min(last_recovered_idx + recovery_batch_size, recovery_v));
1114 }
1115 }
1116
1118 {
1119 // When reaching the end of the private ledger, make sure the same
1120 // ledger has been read and swap in private state
1121
1123
1124 if (recovery_v != recovery_store->current_version())
1125 {
1126 throw std::logic_error(fmt::format(
1127 "Private recovery did not reach public ledger seqno: {}/{}",
1128 recovery_store->current_version(),
1129 recovery_v));
1130 }
1131
1132 auto h =
1133 dynamic_cast<MerkleTxHistory*>(recovery_store->get_history().get());
1134 if (h->get_replicated_state_root() != recovery_root)
1135 {
1136 throw std::logic_error(fmt::format(
1137 "Root of public store does not match root of private store at {}",
1138 recovery_v));
1139 }
1140
1141 network.tables->swap_private_maps(*recovery_store.get());
1142 recovery_store.reset();
1143
1144 // Raft should deserialise all security domains when network is opened
1145 consensus->enable_all_domains();
1146
1147 // Snapshots are only generated after recovery is complete
1148 snapshotter->set_snapshot_generation(true);
1149
1150 // Open the service
1151 if (consensus->can_replicate())
1152 {
1153 auto tx = network.tables->create_tx();
1154
1155 {
1156 // Ensure this transition happens at-most-once, by checking that no
1157 // other node has already advanced the state
1158 auto service = tx.ro<ccf::Service>(Tables::SERVICE);
1159 auto active_service = service->get();
1160
1161 if (!active_service.has_value())
1162 {
1163 throw std::logic_error(fmt::format(
1164 "Error in {}: no value in {}", __func__, Tables::SERVICE));
1165 }
1166
1167 if (
1168 active_service->status !=
1170 {
1171 throw std::logic_error(fmt::format(
1172 "Error in {}: current service status is {}",
1173 __func__,
1174 active_service->status));
1175 }
1176 }
1177
1178 // Clear recovery shares that were submitted to initiate the recovery
1179 // procedure
1181
1182 // Shares for the new ledger secret can only be issued now, once the
1183 // previous ledger secrets have been recovered
1184 share_manager.issue_recovery_shares(tx);
1185
1187 {
1188 throw std::logic_error("Service could not be opened");
1189 }
1190
1191 // Trigger a snapshot (at next signature) to ensure we have a working
1192 // snapshot signed by the current (now new) service identity, in case
1193 // we need to recover soon again.
1194 trigger_snapshot(tx);
1195
1196 if (tx.commit() != ccf::kv::CommitResult::SUCCESS)
1197 {
1198 throw std::logic_error(
1199 "Could not commit transaction when finishing network recovery");
1200 }
1201 }
1202 recovered_encrypted_ledger_secrets.clear();
1203 reset_data(quote_info.quote);
1204 reset_data(quote_info.endorsements);
1206 }
1207
1209 {
1210 // This hook is necessary to adjust the version at which the last ledger
1211 // secret before recovery is recorded in the store. This can only be
1212 // fired once, after the recovery shares for the post-recovery ledger
1213 // secret are issued.
1214 network.tables->set_map_hook(
1215 network.encrypted_ledger_secrets.get_name(),
1216 network.encrypted_ledger_secrets.wrap_map_hook(
1217 [this](
1218 ccf::kv::Version version,
1219 const EncryptedLedgerSecretsInfo::Write& w)
1221 if (!w.has_value())
1222 {
1223 throw std::logic_error(fmt::format(
1224 "Unexpected removal from {} table",
1225 network.encrypted_ledger_secrets.get_name()));
1226 }
1227
1228 network.ledger_secrets->adjust_previous_secret_stored_version(
1229 version);
1230
1231 network.tables->unset_map_hook(
1232 network.encrypted_ledger_secrets.get_name());
1233
1234 return ccf::kv::ConsensusHookPtr(nullptr);
1235 }));
1236 }
1237
1238 //
1239 // funcs in state "readingPublicLedger" or "readingPrivateLedger"
1240 //
1242 {
1243 std::lock_guard<pal::Mutex> guard(lock);
1244
1245 if (is_reading_public_ledger())
1246 {
1247 recover_public_ledger_end_unsafe();
1248 }
1249 else if (is_reading_private_ledger())
1250 {
1251 recover_private_ledger_end_unsafe();
1252 }
1253 else
1254 {
1256 "Node in state {} cannot finalise ledger recovery", sm.value());
1257 return;
1258 }
1259 }
1260
1261 //
1262 // funcs in state "partOfPublicNetwork"
1263 //
1265 {
1266 recovery_store = std::make_shared<ccf::kv::Store>(
1267 true /* Check transactions in order */,
1268 true /* Make use of historical secrets */);
1269 auto recovery_history = std::make_shared<MerkleTxHistory>(
1270 *recovery_store.get(),
1271 self,
1272 *node_sign_kp,
1273 sig_tx_interval,
1274 sig_ms_interval,
1275 false /* No signature timer on recovery_history */);
1276
1277 auto recovery_encryptor = make_encryptor();
1278
1279 recovery_store->set_history(recovery_history);
1280 recovery_store->set_encryptor(recovery_encryptor);
1281
1282 // Record real store version and root
1283 recovery_v = network.tables->current_version();
1284 auto h = dynamic_cast<MerkleTxHistory*>(history.get());
1285 recovery_root = h->get_replicated_state_root();
1286
1287 if (startup_snapshot_info)
1288 {
1289 std::vector<ccf::kv::Version> view_history_;
1291 deserialise_snapshot(
1292 recovery_store,
1293 startup_snapshot_info->raw,
1294 hooks,
1295 &view_history_,
1296 false,
1297 config.recover.previous_service_identity);
1298 startup_snapshot_info.reset();
1299 }
1300
1302 "Recovery store successfully setup at {}. Target recovery seqno: {}",
1303 recovery_store->current_version(),
1304 recovery_v);
1305 }
1306
1308 {
1309 share_manager.shuffle_recovery_shares(tx);
1310 }
1311
1313 {
1314 auto tx_ = static_cast<ccf::kv::CommittableTx*>(&tx);
1315 if (tx_ == nullptr)
1316 {
1317 throw std::logic_error("Could not cast tx to CommittableTx");
1318 }
1319 tx_->set_flag(
1321 }
1322
1324 {
1325 auto committable_tx = static_cast<ccf::kv::CommittableTx*>(&tx);
1326 if (committable_tx == nullptr)
1327 {
1328 throw std::logic_error("Could not cast tx to CommittableTx");
1329 }
1330 committable_tx->set_flag(
1332 }
1333
1335 ccf::kv::Tx& tx,
1336 const std::optional<std::vector<std::string>>& interfaces =
1337 std::nullopt) override
1338 {
1339 if (!network.identity)
1340 {
1341 return;
1342 }
1343
1344 num_acme_interfaces = 0;
1345
1346 for (const auto& [iname, interface] : config.network.rpc_interfaces)
1347 {
1348 if (
1349 !interface.endorsement ||
1350 interface.endorsement->authority != Authority::ACME ||
1351 !interface.endorsement->acme_configuration)
1352 {
1353 continue;
1354 }
1355
1356 num_acme_interfaces++;
1357
1358 if (
1359 !interfaces ||
1360 std::find(interfaces->begin(), interfaces->end(), iname) !=
1361 interfaces->end())
1362 {
1363 auto challenge_frontend = find_acme_challenge_frontend();
1364
1365 const std::string& cfg_name =
1366 *interface.endorsement->acme_configuration;
1367 auto cit = config.network.acme->configurations.find(cfg_name);
1368 if (cit == config.network.acme->configurations.end())
1369 {
1370 LOG_INFO_FMT("Unknown ACME configuration '{}'", cfg_name);
1371 continue;
1372 }
1373
1374 if (
1375 !cit->second.directory_url.empty() &&
1376 acme_clients.find(cfg_name) == acme_clients.end())
1377 {
1378 const auto& cfg = cit->second;
1379
1380 auto client = std::make_shared<ACMEClient>(
1381 cfg_name,
1382 cfg,
1383 rpc_map,
1384 rpcsessions,
1385 challenge_frontend,
1386 network.tables,
1387 node_sign_kp);
1388
1389 auto chit = acme_challenge_handlers.find(iname);
1390 if (chit != acme_challenge_handlers.end())
1391 {
1392 client->install_custom_challenge_handler(chit->second);
1393 }
1394
1395 acme_clients.emplace(cfg_name, client);
1396 }
1397
1398 auto client = acme_clients[cfg_name];
1399 if (client && !client->has_active_orders())
1400 {
1401 client->get_certificate(
1402 make_key_pair(network.identity->priv_key), true);
1403 }
1404 }
1405 }
1406 }
1407
1409 const std::vector<std::string>& args,
1410 const std::vector<uint8_t>& input) override
1411 {
1412 HostProcessArguments msg{args};
1413 nlohmann::json j = msg;
1414 auto json = j.dump();
1416 "Triggering host process launch: {} size={}", json, input.size());
1418 AppMessage::launch_host_process, to_host, json, input);
1419 }
1420
1422 ccf::kv::Tx& tx,
1424 {
1425 std::lock_guard<pal::Mutex> guard(lock);
1426
1427 auto service = tx.rw<Service>(Tables::SERVICE);
1428 auto service_info = service->get();
1429 if (!service_info.has_value())
1430 {
1431 throw std::logic_error(
1432 "Service information cannot be found to transition service to "
1433 "open");
1434 }
1435
1436 // Idempotence: if the service is already open or waiting for recovery
1437 // shares, this function should succeed with no effect
1438 if (
1439 service_info->status == ServiceStatus::WAITING_FOR_RECOVERY_SHARES ||
1440 service_info->status == ServiceStatus::OPEN)
1441 {
1443 "Service in state {} is already open", service_info->status);
1444 return;
1445 }
1446
1447 if (service_info->status == ServiceStatus::RECOVERING)
1448 {
1449 const auto prev_ident =
1450 tx.ro<PreviousServiceIdentity>(Tables::PREVIOUS_SERVICE_IDENTITY)
1451 ->get();
1452 if (!prev_ident.has_value() || !identities.previous.has_value())
1453 {
1454 throw std::logic_error(
1455 "Recovery with service certificates requires both, a previous "
1456 "service identity written to the KV during recovery genesis and a "
1457 "transition_service_to_open proposal that contains previous and "
1458 "next service certificates");
1459 }
1460
1461 const ccf::crypto::Pem from_proposal(
1462 identities.previous->data(), identities.previous->size());
1463 if (prev_ident.value() != from_proposal)
1464 {
1465 throw std::logic_error(fmt::format(
1466 "Previous service identity does not match.\nActual:\n{}\nIn "
1467 "proposal:\n{}",
1468 prev_ident->str(),
1469 from_proposal.str()));
1470 }
1471 }
1472
1473 if (identities.next != service_info->cert)
1474 {
1475 throw std::logic_error(fmt::format(
1476 "Service identity mismatch: the next service identity in the "
1477 "transition_service_to_open proposal does not match the current "
1478 "service identity:\nNext:\n{}\nCurrent:\n{}",
1479 identities.next.str(),
1480 service_info->cert.str()));
1481 }
1482
1483 service_info->previous_service_identity_version =
1484 service->get_version_of_previous_write();
1485
1486 if (is_part_of_public_network())
1487 {
1488 // If the node is in public mode, start accepting member recovery
1489 // shares
1491 service_info->status = ServiceStatus::WAITING_FOR_RECOVERY_SHARES;
1492 service->put(service_info.value());
1493 return;
1494 }
1495 else if (is_part_of_network())
1496 {
1497 // Otherwise, if the node is part of the network. Open the network
1498 // straight away. Recovery shares are allocated to each recovery
1499 // member.
1500 try
1501 {
1502 share_manager.issue_recovery_shares(tx);
1503 }
1504 catch (const std::logic_error& e)
1505 {
1506 throw std::logic_error(
1507 fmt::format("Failed to issue recovery shares: {}", e.what()));
1508 }
1509
1511 trigger_snapshot(tx);
1512 return;
1513 }
1514 else
1515 {
1516 throw std::logic_error(
1517 fmt::format("Node in state {} cannot open service", sm.value()));
1518 }
1519 }
1520
1521 // Decrypts chain of ledger secrets, and writes those to the ledger
1522 // encrypted for each node. On a commit hook for this write, each node
1523 // (including this one!) will begin_private_recovery().
1525 {
1526 std::lock_guard<pal::Mutex> guard(lock);
1528
1529 LedgerSecretsMap recovered_ledger_secrets =
1530 share_manager.restore_recovery_shares_info(
1531 tx, recovered_encrypted_ledger_secrets);
1532
1533 // Broadcast decrypted ledger secrets to other nodes for them to
1534 // initiate private recovery too
1537 tx.wo(network.secrets),
1538 std::move(recovered_ledger_secrets));
1539 }
1540
1541 //
1542 // funcs in state "partOfNetwork" or "partOfPublicNetwork"
1543 //
1544 void tick(std::chrono::milliseconds elapsed)
1545 {
1546 if (
1550 {
1551 return;
1552 }
1553
1554 consensus->periodic(elapsed);
1555
1556 if (sm.check(NodeStartupState::partOfNetwork))
1557 {
1558 const auto tx_id = consensus->get_committed_txid();
1559 indexer->update_strategies(elapsed, {tx_id.first, tx_id.second});
1560 }
1561
1562 n2n_channels->tick(elapsed);
1563 }
1564
1566 {
1567 if (
1571 {
1572 return;
1573 }
1574
1575 consensus->periodic_end();
1576 }
1577
1578 void stop_notice() override
1579 {
1580 stop_noticed = true;
1581 }
1582
1584 {
1585 return stop_noticed;
1586 }
1587
1588 void recv_node_inbound(const uint8_t* data, size_t size)
1589 {
1590 auto [msg_type, from, payload] =
1591 ringbuffer::read_message<node_inbound>(data, size);
1592
1593 auto payload_data = payload.data;
1594 auto payload_size = payload.size;
1595
1596 if (msg_type == NodeMsgType::forwarded_msg)
1597 {
1598 cmd_forwarder->recv_message(from, payload_data, payload_size);
1599 }
1600 else
1601 {
1602 // Only process messages once part of network
1603 if (
1607 {
1609 "Ignoring node msg received too early - current state is {}",
1610 sm.value());
1611 return;
1612 }
1613
1614 switch (msg_type)
1615 {
1616 case channel_msg:
1617 {
1618 n2n_channels->recv_channel_message(
1619 from, payload_data, payload_size);
1620 break;
1621 }
1622
1623 case consensus_msg:
1624 {
1625 consensus->recv_message(from, payload_data, payload_size);
1626 break;
1627 }
1628
1629 default:
1630 {
1631 LOG_FAIL_FMT("Unknown node message type: {}", msg_type);
1632 return;
1633 }
1634 }
1635 }
1636 }
1637
1638 //
1639 // always available
1640 //
1641 bool is_primary() const override
1642 {
1643 return (
1647 consensus->is_primary());
1648 }
1649
1650 bool can_replicate() override
1651 {
1652 return (
1656 consensus->can_replicate());
1657 }
1658
1659 bool is_in_initialised_state() const override
1660 {
1661 return sm.check(NodeStartupState::initialized);
1662 }
1663
1664 bool is_part_of_network() const override
1665 {
1666 return sm.check(NodeStartupState::partOfNetwork);
1667 }
1668
1669 bool is_reading_public_ledger() const override
1670 {
1672 }
1673
1674 bool is_reading_private_ledger() const override
1675 {
1677 }
1678
1679 bool is_part_of_public_network() const override
1680 {
1682 }
1683
1684 bool is_accessible_to_members() const override
1685 {
1686 const auto val = sm.value();
1687 return val == NodeStartupState::partOfNetwork ||
1690 }
1691
1693 {
1694 std::lock_guard<pal::Mutex> guard(lock);
1695 auto s = sm.value();
1697 {
1698 return {s, recovery_v, recovery_store->current_version()};
1699 }
1700 else
1701 {
1702 return {s, std::nullopt, std::nullopt};
1703 }
1704 }
1705
1706 bool rekey_ledger(ccf::kv::Tx& tx) override
1707 {
1708 std::lock_guard<pal::Mutex> guard(lock);
1710
1711 // The ledger should not be re-keyed when the service is not open
1712 // because:
1713 // - While waiting for recovery shares, the submitted shares are stored
1714 // in a public table, encrypted with the ledger secret generated at
1715 // startup of the first recovery node
1716 // - On recovery, historical ledger secrets can only be looked up in the
1717 // ledger once all ledger secrets have been restored
1718 const auto service_status = InternalTablesAccess::get_service_status(tx);
1719 if (
1720 !service_status.has_value() ||
1721 service_status.value() != ServiceStatus::OPEN)
1722 {
1723 LOG_FAIL_FMT("Cannot rekey ledger while the service is not open");
1724 return false;
1725 }
1726
1727 // Effects of ledger rekey are only observed from the next transaction,
1728 // once the local hook on the secrets table has been triggered.
1729
1730 auto new_ledger_secret = make_ledger_secret();
1731 share_manager.issue_recovery_shares(tx, new_ledger_secret);
1734 tx.wo(network.secrets),
1735 std::move(new_ledger_secret));
1736
1737 return true;
1738 }
1739
1741 {
1742 return self;
1743 }
1744
1746 {
1747 std::lock_guard<pal::Mutex> guard(lock);
1748 return startup_seqno;
1749 }
1750
1752 {
1753 return rpcsessions->get_session_metrics();
1754 }
1755
1757 {
1758 std::lock_guard<pal::Mutex> guard(lock);
1759 return self_signed_node_cert;
1760 }
1761
1762 private:
1763 bool is_ip(const std::string_view& hostname)
1764 {
1765 // IP address components are purely numeric. DNS names may be largely
1766 // numeric, but at least the final component (TLD) must not be
1767 // all-numeric. So this distinguishes "1.2.3.4" (an IP address) from
1768 // "1.2.3.c4m" (a DNS name). "1.2.3." is invalid for either, and will
1769 // throw. Attempts to handle IPv6 by also splitting on ':', but this is
1770 // untested.
1771 const auto final_component =
1772 ccf::nonstd::split(ccf::nonstd::split(hostname, ".").back(), ":")
1773 .back();
1774 if (final_component.empty())
1775 {
1776 throw std::runtime_error(fmt::format(
1777 "{} has a trailing period, is not a valid hostname", hostname));
1778 }
1779 for (const auto c : final_component)
1780 {
1781 if (c < '0' || c > '9')
1782 {
1783 return false;
1784 }
1785 }
1786
1787 return true;
1788 }
1789
1790 std::vector<ccf::crypto::SubjectAltName> get_subject_alternative_names()
1791 {
1792 // If no Subject Alternative Name (SAN) is passed in at node creation,
1793 // default to using node's RPC address as single SAN. Otherwise, use
1794 // specified SANs.
1795 if (!config.node_certificate.subject_alt_names.empty())
1796 {
1797 return ccf::crypto::sans_from_string_list(
1798 config.node_certificate.subject_alt_names);
1799 }
1800 else
1801 {
1802 // Construct SANs from RPC interfaces, manually detecting whether each
1803 // is a domain name or IP
1804 std::vector<ccf::crypto::SubjectAltName> sans;
1805 for (const auto& [_, interface] : config.network.rpc_interfaces)
1806 {
1807 auto host = split_net_address(interface.published_address).first;
1808 sans.push_back({host, is_ip(host)});
1809 }
1810 return sans;
1811 }
1812 }
1813
1814 void accept_node_tls_connections()
1815 {
1816 // Accept TLS connections, presenting self-signed (i.e. non-endorsed)
1817 // node certificate.
1818 rpcsessions->set_node_cert(
1819 self_signed_node_cert, node_sign_kp->private_key_pem());
1820 LOG_INFO_FMT("Node TLS connections now accepted");
1821 }
1822
1823 void accept_network_tls_connections()
1824 {
1825 // Accept TLS connections, presenting node certificate signed by network
1826 // certificate
1828 endorsed_node_cert.has_value(),
1829 "Node certificate should be endorsed before accepting endorsed "
1830 "client "
1831 "connections");
1832 rpcsessions->set_network_cert(
1833 endorsed_node_cert.value(), node_sign_kp->private_key_pem());
1834 LOG_INFO_FMT("Network TLS connections now accepted");
1835 }
1836
1837 auto find_frontend(ActorsType actor)
1838 {
1839 auto fe = rpc_map->find(actor);
1840 if (!fe.has_value())
1841 {
1842 throw std::logic_error(
1843 fmt::format("Cannot find {} frontend", (int)actor));
1844 }
1845 return fe.value();
1846 }
1847
1848 void open_frontend(ActorsType actor)
1849 {
1850 find_frontend(actor)->open();
1851 }
1852
1853 void open_user_frontend()
1854 {
1855 open_frontend(ActorsType::users);
1856 }
1857
1858 bool is_member_frontend_open_unsafe()
1859 {
1860 return find_frontend(ActorsType::members)->is_open();
1861 }
1862
1863 bool is_member_frontend_open() override
1864 {
1865 std::lock_guard<pal::Mutex> guard(lock);
1866 return is_member_frontend_open_unsafe();
1867 }
1868
1869 bool is_user_frontend_open() override
1870 {
1871 std::lock_guard<pal::Mutex> guard(lock);
1872 return find_frontend(ActorsType::users)->is_open();
1873 }
1874
1875 std::shared_ptr<ACMERpcFrontend> find_acme_challenge_frontend()
1876 {
1877 auto acme_challenge_opt = rpc_map->find(ActorsType::acme_challenge);
1878 if (!acme_challenge_opt)
1879 {
1880 throw std::runtime_error("Missing ACME challenge frontend");
1881 }
1882 return std::static_pointer_cast<ACMERpcFrontend>(*acme_challenge_opt);
1883 }
1884
1885 void open_acme_challenge_frontend()
1886 {
1887 if (config.network.acme && !config.network.acme->configurations.empty())
1888 {
1889 auto fe = find_frontend(ActorsType::acme_challenge);
1890 if (fe)
1891 {
1892 fe->open();
1893 }
1894 }
1895 }
1896
1897 std::vector<uint8_t> serialize_create_request(
1898 View create_view, bool create_consortium = true)
1899 {
1900 CreateNetworkNodeToNode::In create_params;
1901
1902 // False on recovery where the consortium is read from the existing
1903 // ledger
1904 if (create_consortium)
1905 {
1906 create_params.genesis_info = config.start;
1907 }
1908 create_params.recovery_constitution = config.recover.constitution;
1909 LOG_INFO_FMT("serialise_create_request, set recovery_constitution to:");
1910 if (create_params.recovery_constitution.has_value())
1911 {
1912 LOG_INFO_FMT("{}", create_params.recovery_constitution.value());
1913 }
1914 else
1915 {
1916 LOG_INFO_FMT("No recovery constitution provided");
1917 }
1918
1919 create_params.node_id = self;
1920 create_params.certificate_signing_request = node_sign_kp->create_csr(
1921 config.node_certificate.subject_name, subject_alt_names);
1922 create_params.node_endorsed_certificate =
1923 ccf::crypto::create_endorsed_cert(
1924 create_params.certificate_signing_request,
1925 config.startup_host_time,
1926 config.node_certificate.initial_validity_days,
1927 network.identity->priv_key,
1928 network.identity->cert);
1929
1930 // Even though endorsed certificate is updated on (global) hook, history
1931 // requires it to generate signatures
1932 history->set_endorsed_certificate(
1933 create_params.node_endorsed_certificate);
1934
1935 create_params.public_key = node_sign_kp->public_key_pem();
1936 create_params.service_cert = network.identity->cert;
1937 create_params.quote_info = quote_info;
1938 create_params.public_encryption_key = node_encrypt_kp->public_key_pem();
1939 create_params.measurement = node_measurement;
1940 create_params.snp_uvm_endorsements = snp_uvm_endorsements;
1941 create_params.snp_security_policy =
1942 config.attestation.environment.security_policy;
1943
1944 create_params.node_info_network = config.network;
1945 create_params.node_data = config.node_data;
1946 create_params.service_data = config.service_data;
1947 create_params.create_txid = {create_view, last_recovered_signed_idx + 1};
1948
1949 const auto body = nlohmann::json(create_params).dump();
1950
1951 ::http::Request request(
1952 fmt::format("/{}/{}", get_actor_prefix(ActorsType::nodes), "create"));
1953 request.set_header(
1954 ccf::http::headers::CONTENT_TYPE,
1955 ccf::http::headervalues::contenttype::JSON);
1956
1957 request.set_body(body);
1958
1959 return request.build_request();
1960 }
1961
1962 bool extract_create_result(const std::shared_ptr<RpcContext>& ctx)
1963 {
1964 if (ctx == nullptr)
1965 {
1966 LOG_FAIL_FMT("Expected non-null context");
1967 return false;
1968 }
1969
1970 const auto status = ctx->get_response_status();
1971 if (status != HTTP_STATUS_OK)
1972 {
1974 "Create response is error: {} {}",
1975 status,
1976 http_status_str((http_status)status));
1977 return false;
1978 }
1979
1980 const auto body = nlohmann::json::parse(ctx->get_response_body());
1981 if (!body.is_boolean())
1982 {
1983 LOG_FAIL_FMT("Expected boolean body in create response");
1985 "Expected boolean body in create response: {}", body.dump());
1986 return false;
1987 }
1988
1989 return body;
1990 }
1991
1992 bool send_create_request(const std::vector<uint8_t>& packed)
1993 {
1994 auto node_session = std::make_shared<SessionContext>(
1995 InvalidSessionId, self_signed_node_cert.raw());
1996 auto ctx = make_rpc_context(node_session, packed);
1997
1998 std::shared_ptr<ccf::RpcHandler> search =
1999 ::http::fetch_rpc_handler(ctx, this->rpc_map);
2000
2001 search->process(ctx);
2002
2003 return extract_create_result(ctx);
2004 }
2005
2006 void create_and_send_boot_request(
2007 View create_view, bool create_consortium = true)
2008 {
2009 // Service creation transaction is asynchronous to avoid deadlocks
2010 // (e.g. https://github.com/microsoft/CCF/issues/3788)
2011 auto msg = std::make_unique<::threading::Tmsg<NodeStateMsg>>(
2012 [](std::unique_ptr<::threading::Tmsg<NodeStateMsg>> msg) {
2013 if (!msg->data.self.send_create_request(
2014 msg->data.self.serialize_create_request(
2015 msg->data.create_view, msg->data.create_consortium)))
2016 {
2017 throw std::runtime_error(
2018 "Service creation request could not be committed");
2019 }
2020 if (msg->data.create_consortium)
2021 {
2022 msg->data.self.advance_part_of_network();
2023 }
2024 else
2025 {
2026 msg->data.self.advance_part_of_public_network();
2027 }
2028 },
2029 *this,
2030 create_view,
2031 create_consortium);
2032
2034 threading::get_current_thread_id(), std::move(msg));
2035 }
2036
2037 void begin_private_recovery()
2038 {
2040
2041 LOG_INFO_FMT("Beginning private recovery");
2042
2043 setup_private_recovery_store();
2044
2045 reset_recovery_hook();
2046 setup_one_off_secret_hook();
2047
2048 // Start reading private security domain of ledger
2049 last_recovered_idx = recovery_store->current_version();
2050 read_ledger_entries(
2051 last_recovered_idx + 1, last_recovered_idx + recovery_batch_size);
2052
2054 }
2055
2056 void setup_basic_hooks()
2057 {
2058 network.tables->set_map_hook(
2059 network.secrets.get_name(),
2060 network.secrets.wrap_map_hook(
2061 [this](ccf::kv::Version hook_version, const Secrets::Write& w)
2063 // Used to rekey the ledger on a live service
2064 if (!is_part_of_network())
2065 {
2066 // Ledger rekey is not allowed during recovery
2067 return ccf::kv::ConsensusHookPtr(nullptr);
2068 }
2069
2070 const auto& ledger_secrets_for_nodes = w;
2071 if (!ledger_secrets_for_nodes.has_value())
2072 {
2073 throw std::logic_error(fmt::format(
2074 "Unexpected removal from {} table",
2075 network.secrets.get_name()));
2076 }
2077
2078 for (const auto& [node_id, encrypted_ledger_secrets] :
2079 ledger_secrets_for_nodes.value())
2080 {
2081 if (node_id != self)
2082 {
2083 // Only consider ledger secrets for this node
2084 continue;
2085 }
2086
2087 for (const auto& encrypted_ledger_secret :
2088 encrypted_ledger_secrets)
2089 {
2090 auto plain_ledger_secret = LedgerSecretsBroadcast::decrypt(
2091 node_encrypt_kp, encrypted_ledger_secret.encrypted_secret);
2092
2093 // When rekeying, set the encryption key for the next version
2094 // onward (backups deserialise this transaction with the
2095 // previous ledger secret)
2096 network.ledger_secrets->set_secret(
2097 hook_version + 1,
2098 std::make_shared<LedgerSecret>(
2099 std::move(plain_ledger_secret), hook_version));
2100 }
2101 }
2102
2103 return ccf::kv::ConsensusHookPtr(nullptr);
2104 }));
2105
2106 network.tables->set_global_hook(
2107 network.secrets.get_name(),
2108 network.secrets.wrap_commit_hook([this](
2109 ccf::kv::Version hook_version,
2110 const Secrets::Write& w) {
2111 // Used on recovery to initiate private recovery on backup nodes.
2112 if (!is_part_of_public_network())
2113 {
2114 return;
2115 }
2116
2117 const auto& ledger_secrets_for_nodes = w;
2118 if (!ledger_secrets_for_nodes.has_value())
2119 {
2120 throw std::logic_error(fmt::format(
2121 "Unexpected removal from {} table", network.secrets.get_name()));
2122 }
2123
2124 for (const auto& [node_id, encrypted_ledger_secrets] :
2125 ledger_secrets_for_nodes.value())
2126 {
2127 if (node_id != self)
2128 {
2129 // Only consider ledger secrets for this node
2130 continue;
2131 }
2132
2133 LedgerSecretsMap restored_ledger_secrets = {};
2134 for (const auto& encrypted_ledger_secret : encrypted_ledger_secrets)
2135 {
2136 // On rekey, the version is inferred from the version at which
2137 // the hook is executed. Otherwise, on recovery, use the
2138 // version read from the write set.
2139 if (!encrypted_ledger_secret.version.has_value())
2140 {
2141 throw std::logic_error(fmt::format(
2142 "Commit hook at seqno {} for table {}: no version for "
2143 "encrypted ledger secret",
2144 hook_version,
2145 network.secrets.get_name()));
2146 }
2147
2148 auto plain_ledger_secret = LedgerSecretsBroadcast::decrypt(
2149 node_encrypt_kp, encrypted_ledger_secret.encrypted_secret);
2150
2151 restored_ledger_secrets.emplace(
2152 encrypted_ledger_secret.version.value(),
2153 std::make_shared<LedgerSecret>(
2154 std::move(plain_ledger_secret),
2155 encrypted_ledger_secret.previous_secret_stored_version));
2156 }
2157
2158 if (!restored_ledger_secrets.empty())
2159 {
2160 // When recovering, restore ledger secrets and trigger end of
2161 // recovery protocol (backup only)
2162 network.ledger_secrets->restore_historical(
2163 std::move(restored_ledger_secrets));
2164 begin_private_recovery();
2165 return;
2166 }
2167 }
2168
2170 "Found no ledger secrets for this node ({}) in global commit hook "
2171 "for {} @ {}",
2172 self,
2173 network.secrets.get_name(),
2174 hook_version);
2175 }));
2176
2177 network.tables->set_global_hook(
2178 network.nodes.get_name(),
2179 network.nodes.wrap_commit_hook(
2180 [this](ccf::kv::Version hook_version, const Nodes::Write& w) {
2181 std::vector<NodeId> retired_committed_nodes;
2182 for (const auto& [node_id, node_info] : w)
2183 {
2184 if (node_info.has_value() && node_info->retired_committed)
2185 {
2186 retired_committed_nodes.push_back(node_id);
2187 }
2188 }
2189 consensus->set_retired_committed(
2190 hook_version, retired_committed_nodes);
2191 }));
2192
2193 // Service-endorsed certificate is passed to history as early as _local_
2194 // commit since a new node may become primary (and thus, e.g. generate
2195 // signatures) before the transaction that added it is _globally_
2196 // committed (see https://github.com/microsoft/CCF/issues/4063). It is OK
2197 // if this transaction is rolled back as the node will no longer be part
2198 // of the service.
2199 network.tables->set_map_hook(
2200 network.node_endorsed_certificates.get_name(),
2201 network.node_endorsed_certificates.wrap_map_hook(
2202 [this](
2203 ccf::kv::Version hook_version,
2204 const NodeEndorsedCertificates::Write& w)
2207 "[local] node_endorsed_certificates local hook at version {}, "
2208 "with {} writes",
2209 hook_version,
2210 w.size());
2211 for (auto const& [node_id, endorsed_certificate] : w)
2212 {
2213 if (node_id != self)
2214 {
2216 "[local] Ignoring endorsed certificate for other node {}",
2217 node_id);
2218 continue;
2219 }
2220
2221 if (!endorsed_certificate.has_value())
2222 {
2224 "[local] Endorsed cert for self ({}) has been deleted", self);
2225 throw std::logic_error(fmt::format(
2226 "Could not find endorsed node certificate for {}", self));
2227 }
2228
2229 std::lock_guard<pal::Mutex> guard(lock);
2230
2231 if (endorsed_node_cert.has_value())
2232 {
2234 "[local] Previous endorsed node cert was:\n{}",
2235 endorsed_node_cert->str());
2236 }
2237
2238 endorsed_node_cert = endorsed_certificate.value();
2240 "[local] Under lock, setting endorsed node cert to:\n{}",
2241 endorsed_node_cert->str());
2242 history->set_endorsed_certificate(endorsed_node_cert.value());
2243 n2n_channels->set_endorsed_node_cert(endorsed_node_cert.value());
2244 }
2245
2246 return ccf::kv::ConsensusHookPtr(nullptr);
2247 }));
2248
2249 network.tables->set_global_hook(
2250 network.node_endorsed_certificates.get_name(),
2251 network.node_endorsed_certificates.wrap_commit_hook(
2252 [this](
2253 ccf::kv::Version hook_version,
2254 const NodeEndorsedCertificates::Write& w) {
2256 "[global] node_endorsed_certificates global hook at version {}, "
2257 "with {} writes",
2258 hook_version,
2259 w.size());
2260 for (auto const& [node_id, endorsed_certificate] : w)
2261 {
2262 if (node_id != self)
2263 {
2265 "[global] Ignoring endorsed certificate for other node {}",
2266 node_id);
2267 continue;
2268 }
2269
2270 if (!endorsed_certificate.has_value())
2271 {
2273 "[global] Endorsed cert for self ({}) has been deleted",
2274 self);
2275 throw std::logic_error(fmt::format(
2276 "Could not find endorsed node certificate for {}", self));
2277 }
2278
2279 std::lock_guard<pal::Mutex> guard(lock);
2280
2281 LOG_INFO_FMT("[global] Accepting network connections");
2282 accept_network_tls_connections();
2283
2284 if (is_member_frontend_open_unsafe())
2285 {
2286 // Also, automatically refresh self-signed node certificate,
2287 // using the same validity period as the endorsed certificate.
2288 // Note that this is only done when the certificate is renewed
2289 // via proposal (i.e. when the member frontend is open), and not
2290 // for the initial addition of the node (the self-signed
2291 // certificate is output to disk then).
2292 auto [valid_from, valid_to] =
2293 ccf::crypto::make_verifier(endorsed_node_cert.value())
2294 ->validity_period();
2296 "[global] Member frontend is open, so refreshing self-signed "
2297 "node cert");
2299 "[global] Previously:\n{}", self_signed_node_cert.str());
2300 self_signed_node_cert = create_self_signed_cert(
2301 node_sign_kp,
2302 config.node_certificate.subject_name,
2303 subject_alt_names,
2304 valid_from,
2305 valid_to);
2306 LOG_INFO_FMT("[global] Now:\n{}", self_signed_node_cert.str());
2307
2308 LOG_INFO_FMT("[global] Accepting node connections");
2309 accept_node_tls_connections();
2310 }
2311 else
2312 {
2313 LOG_INFO_FMT("[global] Member frontend is NOT open");
2315 "[global] Self-signed node cert remains:\n{}",
2316 self_signed_node_cert.str());
2317 }
2318
2319 LOG_INFO_FMT("[global] Opening members frontend");
2320 open_frontend(ActorsType::members);
2321 }
2322 }));
2323
2324 network.tables->set_global_hook(
2325 network.service.get_name(),
2326 network.service.wrap_commit_hook(
2327 [this](ccf::kv::Version hook_version, const Service::Write& w) {
2328 if (!w.has_value())
2329 {
2330 throw std::logic_error("Unexpected deletion in service value");
2331 }
2332
2333 // Service open on historical service has no effect
2334 auto hook_pubk_pem = ccf::crypto::public_key_pem_from_cert(
2336 auto current_pubk_pem =
2337 ccf::crypto::make_key_pair(network.identity->priv_key)
2338 ->public_key_pem();
2339 if (hook_pubk_pem != current_pubk_pem)
2340 {
2342 "Ignoring historical service open at seqno {} for {}",
2343 hook_version,
2344 w->cert.str());
2345 return;
2346 }
2347
2349 "Executing global hook for service table at {}, to service "
2350 "status {}. Cert is:\n{}",
2351 hook_version,
2352 w->status,
2353 w->cert.str());
2354
2355 network.identity->set_certificate(w->cert);
2356 if (w->status == ServiceStatus::OPEN)
2357 {
2358 open_user_frontend();
2359
2360 RINGBUFFER_WRITE_MESSAGE(::consensus::ledger_open, to_host);
2361 LOG_INFO_FMT("Service open at seqno {}", hook_version);
2362 }
2363 }));
2364
2365 network.tables->set_global_hook(
2366 network.acme_certificates.get_name(),
2367 network.acme_certificates.wrap_commit_hook(
2368 [this](
2369 ccf::kv::Version hook_version, const ACMECertificates::Write& w) {
2370 for (auto const& [interface_id, interface] :
2371 config.network.rpc_interfaces)
2372 {
2373 if (interface.endorsement->acme_configuration)
2374 {
2375 auto cit = w.find(*interface.endorsement->acme_configuration);
2376 if (cit != w.end())
2377 {
2379 "ACME: new certificate for interface '{}' with "
2380 "configuration '{}'",
2381 interface_id,
2382 *interface.endorsement->acme_configuration);
2383 rpcsessions->set_cert(
2385 *cit->second,
2386 network.identity->priv_key,
2387 cit->first);
2388 }
2389 }
2390 }
2391 }));
2392 }
2393
2394 ccf::kv::Version get_last_recovered_signed_idx() override
2395 {
2396 // On recovery, only one node recovers the public ledger and is thus
2397 // aware of the version at which the new ledger secret is applicable
2398 // from. If the primary changes while the network is public-only, the
2399 // new primary should also know at which version the new ledger secret
2400 // is applicable from.
2401 std::lock_guard<pal::Mutex> guard(lock);
2402 return last_recovered_signed_idx;
2403 }
2404
2405 void setup_recovery_hook()
2406 {
2407 network.tables->set_map_hook(
2408 network.encrypted_ledger_secrets.get_name(),
2409 network.encrypted_ledger_secrets.wrap_map_hook(
2410 [this](
2411 ccf::kv::Version version,
2412 const EncryptedLedgerSecretsInfo::Write& w)
2414 auto encrypted_ledger_secret_info = w;
2415 if (!encrypted_ledger_secret_info.has_value())
2416 {
2417 throw std::logic_error(fmt::format(
2418 "Unexpected removal from {} table",
2419 network.encrypted_ledger_secrets.get_name()));
2420 }
2421
2422 // If the version of the next ledger secret is not set, deduce it
2423 // from the hook version (i.e. ledger rekey)
2424 if (!encrypted_ledger_secret_info->next_version.has_value())
2425 {
2426 encrypted_ledger_secret_info->next_version = version + 1;
2427 }
2428
2429 if (encrypted_ledger_secret_info->previous_ledger_secret
2430 .has_value())
2431 {
2432 LOG_DEBUG_FMT(
2433 "Recovering encrypted ledger secret valid at seqno {}",
2434 encrypted_ledger_secret_info->previous_ledger_secret->version);
2435 }
2436
2437 recovered_encrypted_ledger_secrets.emplace_back(
2438 std::move(encrypted_ledger_secret_info.value()));
2439
2440 return ccf::kv::ConsensusHookPtr(nullptr);
2441 }));
2442 }
2443
2444 void reset_recovery_hook()
2445 {
2446 network.tables->unset_map_hook(
2447 network.encrypted_ledger_secrets.get_name());
2448 }
2449
2450 void setup_n2n_channels(
2451 const std::optional<ccf::crypto::Pem>& endorsed_node_certificate_ =
2452 std::nullopt)
2453 {
2454 // If the endorsed node certificate is available at the time the
2455 // consensus/node-to-node channels are initialised, use it (i.e. join).
2456 // Otherwise, specify it later, on endorsed certificate table hook (i.e.
2457 // start or recover).
2458 n2n_channels->initialize(
2459 self, network.identity->cert, node_sign_kp, endorsed_node_certificate_);
2460 }
2461
2462 void setup_cmd_forwarder()
2463 {
2464 cmd_forwarder->initialize(self);
2465 }
2466
2467 void setup_history()
2468 {
2469 if (history)
2470 {
2471 throw std::logic_error("History already initialised");
2472 }
2473
2474 history = std::make_shared<MerkleTxHistory>(
2475 *network.tables.get(),
2476 self,
2477 *node_sign_kp,
2478 sig_tx_interval,
2479 sig_ms_interval,
2480 false /* start timed signatures after first tx */);
2481 network.tables->set_history(history);
2482 }
2483
2484 void setup_encryptor()
2485 {
2486 if (encryptor)
2487 {
2488 throw std::logic_error("Encryptor already initialised");
2489 }
2490
2491 encryptor = make_encryptor();
2492 network.tables->set_encryptor(encryptor);
2493 }
2494
2495 void setup_consensus(
2496 ServiceStatus service_status,
2497 ReconfigurationType reconfiguration_type,
2498 bool public_only = false,
2499 const std::optional<ccf::crypto::Pem>& endorsed_node_certificate_ =
2500 std::nullopt)
2501 {
2502 setup_n2n_channels(endorsed_node_certificate_);
2503 setup_cmd_forwarder();
2504
2505 auto shared_state = std::make_shared<aft::State>(self);
2506
2507 auto node_client = std::make_shared<HTTPNodeClient>(
2508 rpc_map, node_sign_kp, self_signed_node_cert, endorsed_node_cert);
2509
2510 ccf::kv::MembershipState membership_state =
2512
2513 consensus = std::make_shared<RaftType>(
2514 consensus_config,
2515 std::make_unique<aft::Adaptor<ccf::kv::Store>>(network.tables),
2516 std::make_unique<::consensus::LedgerEnclave>(writer_factory),
2517 n2n_channels,
2518 shared_state,
2519 node_client,
2520 public_only,
2521 membership_state,
2522 reconfiguration_type);
2523
2524 network.tables->set_consensus(consensus);
2525 network.tables->set_snapshotter(snapshotter);
2526
2527 // When a node is added, even locally, inform consensus so that it
2528 // can add a new active configuration.
2529 network.tables->set_map_hook(
2530 network.nodes.get_name(),
2531 network.nodes.wrap_map_hook(
2532 [](ccf::kv::Version version, const Nodes::Write& w)
2534 return std::make_unique<ConfigurationChangeHook>(version, w);
2535 }));
2536
2537 // Note: The Signatures hook and SerialisedMerkleTree hook are separate
2538 // because the signature and the Merkle tree are recorded in distinct
2539 // tables (for serialisation performance reasons). However here, they are
2540 // expected to always be called together and for the same version as they
2541 // are always written by each signature transaction.
2542
2543 network.tables->set_map_hook(
2544 network.signatures.get_name(),
2545 network.signatures.wrap_map_hook(
2546 [s = this->snapshotter](
2547 ccf::kv::Version version, const Signatures::Write& w) {
2548 assert(w.has_value());
2549 auto sig = w.value();
2550 s->record_signature(version, sig.sig, sig.node, sig.cert);
2551 return ccf::kv::ConsensusHookPtr(nullptr);
2552 }));
2553
2554 network.tables->set_map_hook(
2555 network.serialise_tree.get_name(),
2556 network.serialise_tree.wrap_map_hook(
2557 [s = this->snapshotter](
2558 ccf::kv::Version version, const SerialisedMerkleTree::Write& w) {
2559 assert(w.has_value());
2560 auto tree = w.value();
2561 s->record_serialised_tree(version, tree);
2562 return ccf::kv::ConsensusHookPtr(nullptr);
2563 }));
2564
2565 network.tables->set_map_hook(
2566 network.snapshot_evidence.get_name(),
2567 network.snapshot_evidence.wrap_map_hook(
2568 [s = this->snapshotter](
2569 ccf::kv::Version version, const SnapshotEvidence::Write& w) {
2570 assert(w.has_value());
2571 auto snapshot_evidence = w.value();
2572 s->record_snapshot_evidence_idx(version, snapshot_evidence);
2573 return ccf::kv::ConsensusHookPtr(nullptr);
2574 }));
2575
2576 setup_basic_hooks();
2577 }
2578
2579 void setup_snapshotter()
2580 {
2581 if (snapshotter)
2582 {
2583 throw std::logic_error("Snapshotter already initialised");
2584 }
2585 snapshotter = std::make_shared<Snapshotter>(
2586 writer_factory, network.tables, config.snapshot_tx_interval);
2587 }
2588
2589 void read_ledger_entries(::consensus::Index from, ::consensus::Index to)
2590 {
2592 ::consensus::ledger_get_range,
2593 to_host,
2594 from,
2595 to,
2597 }
2598
2599 void ledger_truncate(::consensus::Index idx, bool recovery_mode = false)
2600 {
2602 ::consensus::ledger_truncate, to_host, idx, recovery_mode);
2603 }
2604
2605 void setup_acme_clients()
2606 {
2607 if (!config.network.acme || config.network.acme->configurations.empty())
2608 {
2609 return;
2610 }
2611
2612 open_acme_challenge_frontend();
2613
2614 const auto& ifaces = config.network.rpc_interfaces;
2615 num_acme_interfaces =
2616 std::count_if(ifaces.begin(), ifaces.end(), [](const auto& id_iface) {
2617 return id_iface.second.endorsement->authority == Authority::ACME;
2618 });
2619
2620 if (num_acme_interfaces > 0)
2621 {
2622 using namespace threading;
2623
2624 // Start task to periodically check whether any of the certs are
2625 // expired.
2626 auto msg = std::make_unique<::threading::Tmsg<NodeStateMsg>>(
2627 [](std::unique_ptr<::threading::Tmsg<NodeStateMsg>> msg) {
2628 auto& state = msg->data.self;
2629
2630 if (state.consensus && state.consensus->can_replicate())
2631 {
2632 if (state.acme_clients.size() != state.num_acme_interfaces)
2633 {
2634 auto tx = state.network.tables->create_tx();
2635 state.trigger_acme_refresh(tx);
2636 tx.commit();
2637 }
2638 else
2639 {
2640 for (auto& [cfg_name, client] : state.acme_clients)
2641 {
2642 if (client)
2643 {
2644 client->check_expiry(
2645 state.network.tables, state.network.identity);
2646 }
2647 }
2648 }
2649 }
2650
2651 auto delay = std::chrono::minutes(1);
2653 std::move(msg), delay);
2654 },
2655 *this);
2656
2658 std::move(msg), std::chrono::seconds(2));
2659 }
2660 }
2661
2662 public:
2663 void set_n2n_message_limit(size_t message_limit)
2664 {
2665 n2n_channels->set_message_limit(message_limit);
2666 }
2667
2668 void set_n2n_idle_timeout(std::chrono::milliseconds idle_timeout)
2669 {
2670 n2n_channels->set_idle_timeout(idle_timeout);
2671 }
2672
2673 virtual const StartupConfig& get_node_config() const override
2674 {
2675 return config;
2676 }
2677
2679 {
2680 return network.identity->cert;
2681 }
2682
2684 const ccf::NodeInfoNetwork::RpcInterfaceID& interface_id,
2685 std::shared_ptr<ACMEChallengeHandler> h) override
2686 {
2687 acme_challenge_handlers[interface_id] = h;
2688 }
2689
2690 // Stop-gap until it becomes easier to use other HTTP clients
2691 virtual void make_http_request(
2692 const ::http::URL& url,
2693 ::http::Request&& req,
2694 std::function<
2695 bool(http_status status, http::HeaderMap&&, std::vector<uint8_t>&&)>
2696 callback,
2697 const std::vector<std::string>& ca_certs = {},
2698 const std::string& app_protocol = "HTTP1",
2699 bool authenticate_as_node_client_certificate = false) override
2700 {
2701 std::optional<ccf::crypto::Pem> client_cert = std::nullopt;
2702 std::optional<ccf::crypto::Pem> client_cert_key = std::nullopt;
2703 if (authenticate_as_node_client_certificate)
2704 {
2705 client_cert =
2706 endorsed_node_cert ? *endorsed_node_cert : self_signed_node_cert;
2707 client_cert_key = node_sign_kp->private_key_pem();
2708 }
2709
2710 auto ca = std::make_shared<::tls::CA>(ca_certs, true);
2711 std::shared_ptr<::tls::Cert> ca_cert =
2712 std::make_shared<::tls::Cert>(ca, client_cert, client_cert_key);
2713 auto client = rpcsessions->create_client(ca_cert, app_protocol);
2714 client->connect(
2715 url.host,
2716 url.port,
2717 [callback](
2718 http_status status,
2719 http::HeaderMap&& headers,
2720 std::vector<uint8_t>&& data) {
2721 return callback(status, std::move(headers), std::move(data));
2722 });
2723 client->send_request(std::move(req));
2724 }
2725
2726 void write_snapshot(std::span<uint8_t> snapshot_buf, size_t request_id)
2727 {
2728 snapshotter->write_snapshot(snapshot_buf, request_id);
2729 }
2730
2731 virtual std::shared_ptr<ccf::kv::Store> get_store() override
2732 {
2733 return network.tables;
2734 }
2735
2737 {
2738 return writer_factory;
2739 }
2740 };
2741}
#define CCF_ASSERT_FMT(expr,...)
Definition ccf_assert.h:10
Definition raft_types.h:48
Definition raft.h:96
Definition node_interface.h:22
static std::optional< HostData > get_host_data(const QuoteInfo &quote_info)
Definition quote.cpp:128
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)
Definition quote.cpp:182
static std::optional< pal::PlatformAttestationMeasurement > get_measurement(const QuoteInfo &quote_info)
Definition quote.cpp:111
static std::optional< ServiceStatus > get_service_status(ccf::kv::ReadOnlyTx &tx)
Definition internal_tables_access.h:401
static bool open_service(ccf::kv::Tx &tx)
Definition internal_tables_access.h:354
static std::map< NodeId, NodeInfo > get_trusted_nodes(ccf::kv::ReadOnlyTx &tx)
Definition internal_tables_access.h:285
static void broadcast_some(std::map< NodeId, NodeInfo > &&nodes, SecretsWriteHandle *secrets, const LedgerSecretsMap &some_ledger_secrets)
Definition secret_broadcast.h:19
static void broadcast_new(std::map< NodeId, NodeInfo > &&nodes, SecretsWriteHandle *secrets, LedgerSecretPtr &&new_ledger_secret)
Definition secret_broadcast.h:46
static std::vector< uint8_t > decrypt(const ccf::crypto::RSAKeyPairPtr &encryption_key, const std::vector< uint8_t > &cipher)
Definition secret_broadcast.h:70
Definition node_state.h:74
void write_snapshot(std::span< uint8_t > snapshot_buf, size_t request_id)
Definition node_state.h:2726
void start_ledger_recovery_unsafe()
Definition node_state.h:853
bool rekey_ledger(ccf::kv::Tx &tx) override
Definition node_state.h:1706
bool is_part_of_public_network() const override
Definition node_state.h:1679
NodeState(ringbuffer::AbstractWriterFactory &writer_factory, NetworkState &network, std::shared_ptr< RPCSessions > rpcsessions, ccf::crypto::CurveID curve_id_)
Definition node_state.h:222
void setup_one_off_secret_hook()
Definition node_state.h:1208
void tick_end()
Definition node_state.h:1565
virtual std::shared_ptr< ccf::kv::Store > get_store() override
Definition node_state.h:2731
void recover_private_ledger_entries(const std::vector< uint8_t > &entries)
Definition node_state.h:1045
void trigger_acme_refresh(ccf::kv::Tx &tx, const std::optional< std::vector< std::string > > &interfaces=std::nullopt) override
Definition node_state.h:1334
void recv_node_inbound(const uint8_t *data, size_t size)
Definition node_state.h:1588
bool is_reading_private_ledger() const override
Definition node_state.h:1674
void trigger_ledger_chunk(ccf::kv::Tx &tx) override
Definition node_state.h:1312
virtual ringbuffer::AbstractWriterFactory & get_writer_factory() override
Definition node_state.h:2736
bool is_primary() const override
Definition node_state.h:1641
virtual ccf::crypto::Pem get_network_cert() override
Definition node_state.h:2678
void recover_public_ledger_end_unsafe()
Definition node_state.h:982
SessionMetrics get_session_metrics() override
Definition node_state.h:1751
void initiate_join()
Definition node_state.h:788
void advance_part_of_network()
Definition node_state.h:971
void advance_part_of_public_network()
Definition node_state.h:963
ccf::kv::Version get_startup_snapshot_seqno() override
Definition node_state.h:1745
void auto_refresh_jwt_keys()
Definition node_state.h:817
void recover_ledger_end()
Definition node_state.h:1241
void initiate_join_unsafe()
Definition node_state.h:558
void stop_notice() override
Definition node_state.h:1578
virtual const StartupConfig & get_node_config() const override
Definition node_state.h:2673
void set_n2n_message_limit(size_t message_limit)
Definition node_state.h:2663
bool can_replicate() override
Definition node_state.h:1650
void transition_service_to_open(ccf::kv::Tx &tx, AbstractGovernanceEffects::ServiceIdentities identities) override
Definition node_state.h:1421
QuoteVerificationResult verify_quote(ccf::kv::ReadOnlyTx &tx, const QuoteInfo &quote_info_, const std::vector< uint8_t > &expected_node_public_key_der, pal::PlatformAttestationMeasurement &measurement) override
Definition node_state.h:239
bool is_reading_public_ledger() const override
Definition node_state.h:1669
void launch_node()
Definition node_state.h:286
void initiate_quote_generation()
Definition node_state.h:399
NodeCreateInfo create(StartType start_type_, StartupConfig &&config_, std::vector< uint8_t > &&startup_snapshot_)
Definition node_state.h:462
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_, size_t sig_tx_interval_, size_t sig_ms_interval_)
Definition node_state.h:252
void initiate_private_recovery(ccf::kv::Tx &tx) override
Definition node_state.h:1524
void setup_private_recovery_store()
Definition node_state.h:1264
void trigger_snapshot(ccf::kv::Tx &tx) override
Definition node_state.h:1323
bool is_in_initialised_state() const override
Definition node_state.h:1659
virtual void install_custom_acme_challenge_handler(const ccf::NodeInfoNetwork::RpcInterfaceID &interface_id, std::shared_ptr< ACMEChallengeHandler > h) override
Definition node_state.h:2683
bool has_received_stop_notice() override
Definition node_state.h:1583
bool is_part_of_network() const override
Definition node_state.h:1664
void start_join_timer()
Definition node_state.h:794
ExtendedState state() override
Definition node_state.h:1692
void trigger_recovery_shares_refresh(ccf::kv::Tx &tx) override
Definition node_state.h:1307
void set_n2n_idle_timeout(std::chrono::milliseconds idle_timeout)
Definition node_state.h:2668
NodeId get_node_id() const
Definition node_state.h:1740
void tick(std::chrono::milliseconds elapsed)
Definition node_state.h:1544
void trigger_host_process_launch(const std::vector< std::string > &args, const std::vector< uint8_t > &input) override
Definition node_state.h:1408
void recover_public_ledger_entries(const std::vector< uint8_t > &entries)
Definition node_state.h:868
void recover_private_ledger_end_unsafe()
Definition node_state.h:1117
ccf::crypto::Pem get_self_signed_certificate() override
Definition node_state.h:1756
size_t get_jwt_attempts() override
Definition node_state.h:845
bool is_accessible_to_members() const override
Definition node_state.h:1684
virtual void make_http_request(const ::http::URL &url, ::http::Request &&req, std::function< bool(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:2691
Definition share_manager.h:155
static void clear_submitted_recovery_shares(ccf::kv::Tx &tx)
Definition share_manager.h:567
void issue_recovery_shares(ccf::kv::Tx &tx)
Definition share_manager.h:400
Definition key_pair.h:17
Definition pem.h:18
const std::string & str() const
Definition pem.h:46
Definition sha256_hash.h:16
Definition committable_tx.h:18
Definition tx.h:161
M::ReadOnlyHandle * ro(M &m)
Definition tx.h:170
Definition tx.h:202
M::Handle * rw(M &m)
Definition tx.h:213
M::WriteOnlyHandle * wo(M &m)
Definition tx.h:234
static std::vector< uint8_t > get_entry(const uint8_t *&data, size_t &size)
Definition ledger_enclave.h:26
Definition state_machine.h:14
void expect(T state_) const
Definition state_machine.h:24
T value() const
Definition state_machine.h:39
void advance(T state_)
Definition state_machine.h:44
bool check(T state_) const
Definition state_machine.h:34
void set_body(const std::vector< uint8_t > *b)
Definition http_builder.h:74
void set_header(std::string k, const std::string &v)
Definition http_builder.h:45
Definition http_builder.h:106
Definition ring_buffer_types.h:153
static ThreadMessaging & instance()
Definition thread_messaging.h:278
TaskQueue::TimerEntry add_task_after(std::unique_ptr< Tmsg< Payload > > msg, std::chrono::milliseconds ms)
Definition thread_messaging.h:320
void add_task(uint16_t tid, std::unique_ptr< Tmsg< Payload > > msg)
Definition thread_messaging.h:312
StartType
Definition enclave_interface_types.h:113
@ Join
Definition enclave_interface_types.h:115
@ Recover
Definition enclave_interface_types.h:116
@ Start
Definition enclave_interface_types.h:114
llhttp_status http_status
Definition http_status.h:7
@ CFT
Definition consensus_type.h:7
ReconfigurationType
Definition reconfiguration_type.h:6
@ ONE_TRANSACTION
Definition reconfiguration_type.h:7
#define LOG_INFO_FMT
Definition logger.h:395
#define LOG_TRACE_FMT
Definition logger.h:378
#define LOG_DEBUG_FMT
Definition logger.h:380
#define LOG_FAIL_FMT
Definition logger.h:396
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
VerifierPtr make_verifier(const std::vector< uint8_t > &cert)
Definition verifier.cpp:18
CurveID
Definition curve.h:18
KeyPairPtr make_key_pair(CurveID curve_id=service_identity_curve_choice)
Definition key_pair.cpp:35
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:71
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
uint64_t Version
Definition version.h:8
@ SUCCESS
Definition kv_types.h:247
ApplyResult
Definition kv_types.h:339
@ PASS_SIGNATURE
Definition kv_types.h:341
@ FAIL
Definition kv_types.h:348
MembershipState
Definition kv_types.h:153
std::vector< ConsensusHookPtr > ConsensusHookPtrs
Definition hooks.h:22
std::mutex Mutex
Definition locking.h:17
uint16_t get_current_thread_id()
Definition thread_local.cpp:9
Definition app_interface.h:15
constexpr auto get_actor_prefix(ActorsType at)
Definition actors.h:31
NodeId compute_node_id_from_kp(const ccf::crypto::KeyPairPtr &node_sign_kp)
Definition nodes.h:43
std::shared_ptr< ccf::kv::Store > make_store()
Definition network_tables.h:39
QuoteVerificationResult
Definition quote.h:17
ServiceValue< ServiceInfo > Service
Definition service.h:55
HashedTxHistory< MerkleTreeHistory > MerkleTxHistory
Definition history.h:860
std::tuple< NodeStartupState, std::optional< ccf::kv::Version >, std::optional< ccf::kv::Version > > ExtendedState
Definition node_operation_interface.h:19
std::list< EncryptedLedgerSecretInfo > RecoveredEncryptedLedgerSecrets
Definition share_manager.h:145
view
Definition signatures.h:54
std::map< ccf::kv::Version, LedgerSecretPtr > LedgerSecretsMap
Definition ledger_secrets.h:19
void reset_data(std::vector< uint8_t > &data)
Definition node_state.h:67
LedgerSecretPtr make_ledger_secret()
Definition ledger_secret.h:77
constexpr View VIEW_UNKNOWN
Definition tx_id.h:26
NodeStartupState
Definition node_startup_state.h:10
uint64_t View
Definition tx_id.h:23
ServiceValue< ccf::crypto::Pem > PreviousServiceIdentity
Definition previous_service_identity.h:13
ActorsType
Definition actors.h:11
@ channel_msg
Definition node_types.h:20
@ consensus_msg
Definition node_types.h:21
@ forwarded_msg
Definition node_types.h:22
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:375
Definition perf_client.h:26
Definition consensus_types.h:23
uint64_t Index
Definition ledger_enclave_types.h:11
@ Recovery
Definition ledger_enclave_types.h:15
Definition configuration.h:13
URL parse_url_full(const std::string &url)
Definition http_parser.h:145
Definition json_schema.h:15
std::shared_ptr< AbstractWriter > WriterPtr
Definition ring_buffer_types.h:150
STL namespace.
Definition thread_messaging.h:14
#define RINGBUFFER_WRITE_MESSAGE(MSG,...)
Definition ring_buffer_types.h:255
std::optional< std::string > uvm_endorsements
Definition startup_config.h:67
std::optional< std::string > security_policy
Definition startup_config.h:66
ccf::pal::snp::EndorsementsServers snp_endorsements_servers
Definition startup_config.h:60
Environment environment
Definition startup_config.h:71
ccf::ds::TimeString key_refresh_interval
Definition startup_config.h:52
size_t initial_validity_days
Definition startup_config.h:35
std::string subject_name
Definition startup_config.h:32
ccf::NodeInfoNetwork network
Definition startup_config.h:28
JWT jwt
Definition startup_config.h:56
NodeCertificateInfo node_certificate
Definition startup_config.h:39
Attestation attestation
Definition startup_config.h:75
Definition interface.h:63
ccf::NodeInfoNetwork::NetAddress target_rpc_address
Definition startup_config.h:106
std::vector< uint8_t > service_cert
Definition startup_config.h:108
ccf::ds::TimeString retry_timeout
Definition startup_config.h:107
bool follow_redirect
Definition startup_config.h:109
std::optional< std::vector< uint8_t > > previous_service_identity
Definition startup_config.h:115
Definition startup_config.h:79
std::string startup_host_time
Definition startup_config.h:83
nlohmann::json node_data
Definition startup_config.h:92
std::string service_subject_name
Definition startup_config.h:88
Recover recover
Definition startup_config.h:119
size_t initial_service_certificate_validity_days
Definition startup_config.h:87
Join join
Definition startup_config.h:111
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
Definition node_call_types.h:85
QuoteInfo quote_info
Definition node_call_types.h:87
std::optional< ccf::crypto::Pem > certificate_signing_request
Definition node_call_types.h:93
std::optional< ccf::kv::Version > startup_seqno
Definition node_call_types.h:92
NodeInfoNetwork node_info_network
Definition node_call_types.h:86
nlohmann::json node_data
Definition node_call_types.h:95
std::optional< ConsensusType > consensus_type
Definition node_call_types.h:91
ccf::crypto::Pem public_encryption_key
Definition node_call_types.h:88
Definition node_call_types.h:99
std::optional< NetworkInfo > network_info
Definition node_call_types.h:162
NodeStatus node_status
Definition node_call_types.h:100
Definition network_state.h:12
std::shared_ptr< LedgerSecrets > ledger_secrets
Definition network_state.h:14
std::unique_ptr< NetworkIdentity > identity
Definition network_state.h:13
std::shared_ptr< ccf::kv::Store > tables
Definition network_tables.h:46
const JwtIssuers jwt_issuers
Definition network_tables.h:157
const Configuration config
Definition network_tables.h:182
const EncryptedLedgerSecretsInfo encrypted_ledger_secrets
Definition network_tables.h:211
const Signatures signatures
Definition network_tables.h:219
Definition node_state.h:62
ccf::crypto::Pem service_cert
Definition node_state.h:64
ccf::crypto::Pem self_signed_node_cert
Definition node_state.h:63
std::string RpcInterfaceID
Definition node_info_network.h:87
Describes a quote (attestation) from trusted hardware.
Definition quote_info.h:26
QuoteFormat format
Quote format.
Definition quote_info.h:28
std::optional< std::vector< uint8_t > > uvm_endorsements
UVM endorsements (SNP-only)
Definition quote_info.h:34
std::vector< uint8_t > quote
Enclave quote.
Definition quote_info.h:30
std::vector< uint8_t > endorsements
Quote endorsements.
Definition quote_info.h:32
Definition session_metrics.h:13
Definition consensus_config.h:11
size_t count_s() const
Definition unit_strings.h:190
Definition measurement.h:116
Definition report_data.h:51