From 46dff13ee5c770a618e7ea95d1c7924b0d92b4e6 Mon Sep 17 00:00:00 2001 From: Amaury Chamayou Date: Mon, 5 Oct 2026 21:08:55 +0100 Subject: [PATCH] Extract NodeState basic hook callbacks Move the six setup_basic_hooks callbacks into typed member helpers and retain small binding lambdas without changing hook semantics. Remove only the registration function's cognitive-complexity suppression. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/node/node_state.h | 577 ++++++++++++++++++++++-------------------- 1 file changed, 301 insertions(+), 276 deletions(-) diff --git a/src/node/node_state.h b/src/node/node_state.h index c8f388c6bbb..289c75ee9ac 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -3273,142 +3273,326 @@ namespace ccf last_recovered_idx + 1, last_recovered_idx + recovery_batch_size); } - // NOLINTNEXTLINE(readability-function-cognitive-complexity) - void setup_basic_hooks() + ccf::kv::ConsensusHookPtr on_secrets_local_commit( + ccf::kv::Version hook_version, const Secrets::Write& w) { - network.tables->set_map_hook( - network.secrets.get_name(), - Secrets::wrap_map_hook( - [this](ccf::kv::Version hook_version, const Secrets::Write& w) - -> ccf::kv::ConsensusHookPtr { - // Used to rekey the ledger on a live service - if (!is_part_of_network()) - { - // Ledger rekey is not allowed during recovery - return {nullptr}; - } + // Used to rekey the ledger on a live service + if (!is_part_of_network()) + { + // Ledger rekey is not allowed during recovery + return {nullptr}; + } - const auto& ledger_secrets_for_nodes = w; - if (!ledger_secrets_for_nodes.has_value()) - { - throw std::logic_error(fmt::format( - "Unexpected removal from {} table", - network.secrets.get_name())); - } + const auto& ledger_secrets_for_nodes = w; + if (!ledger_secrets_for_nodes.has_value()) + { + throw std::logic_error(fmt::format( + "Unexpected removal from {} table", network.secrets.get_name())); + } - for (const auto& [node_id, encrypted_ledger_secrets] : - ledger_secrets_for_nodes.value()) - { - if (node_id != self) - { - // Only consider ledger secrets for this node - continue; - } + for (const auto& [node_id, encrypted_ledger_secrets] : + ledger_secrets_for_nodes.value()) + { + if (node_id != self) + { + // Only consider ledger secrets for this node + continue; + } - for (const auto& encrypted_ledger_secret : - encrypted_ledger_secrets) - { - auto plain_ledger_secret = LedgerSecretsBroadcast::decrypt( - node_encrypt_kp, encrypted_ledger_secret.encrypted_secret); - - // When rekeying, set the encryption key for the next version - // onward (backups deserialise this transaction with the - // previous ledger secret) - auto ledger_secret = std::make_shared( - std::move(plain_ledger_secret), hook_version); - network.ledger_secrets->set_secret( - hook_version + 1, std::move(ledger_secret)); - } - } + for (const auto& encrypted_ledger_secret : encrypted_ledger_secrets) + { + auto plain_ledger_secret = LedgerSecretsBroadcast::decrypt( + node_encrypt_kp, encrypted_ledger_secret.encrypted_secret); + + // When rekeying, set the encryption key for the next version + // onward (backups deserialise this transaction with the + // previous ledger secret) + auto ledger_secret = std::make_shared( + std::move(plain_ledger_secret), hook_version); + network.ledger_secrets->set_secret( + hook_version + 1, std::move(ledger_secret)); + } + } - return {nullptr}; - })); + return {nullptr}; + } - network.tables->set_global_hook( - network.secrets.get_name(), - Secrets::wrap_commit_hook([this]( - ccf::kv::Version hook_version, - const Secrets::Write& w) { - // Used on recovery to initiate private recovery on all nodes. - if (!is_part_of_public_network()) - { - return; - } + void on_secrets_global_commit( + ccf::kv::Version hook_version, const Secrets::Write& w) + { + // Used on recovery to initiate private recovery on all nodes. + if (!is_part_of_public_network()) + { + return; + } - const auto& ledger_secrets_for_nodes = w; - if (!ledger_secrets_for_nodes.has_value()) + const auto& ledger_secrets_for_nodes = w; + if (!ledger_secrets_for_nodes.has_value()) + { + throw std::logic_error(fmt::format( + "Unexpected removal from {} table", network.secrets.get_name())); + } + + for (const auto& [node_id, encrypted_ledger_secrets] : + ledger_secrets_for_nodes.value()) + { + if (node_id != self) + { + // Only consider ledger secrets for this node + continue; + } + + LedgerSecretsMap restored_ledger_secrets = {}; + for (const auto& encrypted_ledger_secret : encrypted_ledger_secrets) + { + // On rekey, the version is inferred from the version at which + // the hook is executed. Otherwise, on recovery, use the + // version read from the write set. + if (!encrypted_ledger_secret.version.has_value()) { throw std::logic_error(fmt::format( - "Unexpected removal from {} table", network.secrets.get_name())); + "Commit hook at seqno {} for table {}: no version for " + "encrypted ledger secret", + hook_version, + network.secrets.get_name())); } - for (const auto& [node_id, encrypted_ledger_secrets] : - ledger_secrets_for_nodes.value()) - { - if (node_id != self) - { - // Only consider ledger secrets for this node - continue; - } + auto plain_ledger_secret = LedgerSecretsBroadcast::decrypt( + node_encrypt_kp, encrypted_ledger_secret.encrypted_secret); - LedgerSecretsMap restored_ledger_secrets = {}; - for (const auto& encrypted_ledger_secret : encrypted_ledger_secrets) - { - // On rekey, the version is inferred from the version at which - // the hook is executed. Otherwise, on recovery, use the - // version read from the write set. - if (!encrypted_ledger_secret.version.has_value()) - { - throw std::logic_error(fmt::format( - "Commit hook at seqno {} for table {}: no version for " - "encrypted ledger secret", - hook_version, - network.secrets.get_name())); - } + restored_ledger_secrets.emplace( + encrypted_ledger_secret.version.value(), + std::make_shared( + std::move(plain_ledger_secret), + encrypted_ledger_secret.previous_secret_stored_version)); + } - auto plain_ledger_secret = LedgerSecretsBroadcast::decrypt( - node_encrypt_kp, encrypted_ledger_secret.encrypted_secret); + if (!restored_ledger_secrets.empty()) + { + // When recovering, restore ledger secrets and trigger end of + // recovery protocol (backup only) + network.ledger_secrets->restore_historical( + std::move(restored_ledger_secrets)); + begin_private_recovery(); + return; + } + } - restored_ledger_secrets.emplace( - encrypted_ledger_secret.version.value(), - std::make_shared( - std::move(plain_ledger_secret), - encrypted_ledger_secret.previous_secret_stored_version)); - } + LOG_INFO_FMT( + "Found no ledger secrets for this node ({}) in global commit hook " + "for {} @ {}", + self, + network.secrets.get_name(), + hook_version); + } - if (!restored_ledger_secrets.empty()) - { - // When recovering, restore ledger secrets and trigger end of - // recovery protocol (backup only) - network.ledger_secrets->restore_historical( - std::move(restored_ledger_secrets)); - begin_private_recovery(); - return; - } + void on_nodes_global_commit( + ccf::kv::Version hook_version, const Nodes::Write& w) + { + std::vector retired_committed_nodes; + for (const auto& [node_id, node_info] : w) + { + if (node_info.has_value() && node_info->retired_committed) + { + retired_committed_nodes.push_back(node_id); + } + } + consensus->set_retired_committed(hook_version, retired_committed_nodes); + } + + ccf::kv::ConsensusHookPtr on_node_endorsed_certificates_local_commit( + ccf::kv::Version hook_version, const NodeEndorsedCertificates::Write& w) + { + LOG_INFO_FMT( + "[local] node_endorsed_certificates local hook at version {}, " + "with {} writes", + hook_version, + w.size()); + for (auto const& [node_id, endorsed_certificate] : w) + { + if (node_id != self) + { + LOG_INFO_FMT( + "[local] Ignoring endorsed certificate for other node {}", node_id); + continue; + } + + if (!endorsed_certificate.has_value()) + { + LOG_FAIL_FMT( + "[local] Endorsed cert for self ({}) has been deleted", self); + throw std::logic_error(fmt::format( + "Could not find endorsed node certificate for {}", self)); + } + + const auto new_endorsed_node_cert = endorsed_certificate.value(); + std::optional previous_endorsed_node_cert; + { + std::lock_guard cert_guard(node_certificates_lock); + previous_endorsed_node_cert = endorsed_node_cert; + endorsed_node_cert = new_endorsed_node_cert; + } + + if (previous_endorsed_node_cert.has_value()) + { + LOG_INFO_FMT( + "[local] Previous endorsed node cert was:\n{}", + previous_endorsed_node_cert->str()); + } + + LOG_INFO_FMT( + "[local] Setting endorsed node cert to:\n{}", + new_endorsed_node_cert.str()); + history->set_endorsed_certificate(new_endorsed_node_cert); + n2n_channels->set_endorsed_node_cert(new_endorsed_node_cert); + } + + return {nullptr}; + } + + void on_node_endorsed_certificates_global_commit( + ccf::kv::Version hook_version, const NodeEndorsedCertificates::Write& w) + { + LOG_INFO_FMT( + "[global] node_endorsed_certificates global hook at version {}, " + "with {} writes", + hook_version, + w.size()); + for (auto const& [node_id, endorsed_certificate] : w) + { + if (node_id != self) + { + LOG_INFO_FMT( + "[global] Ignoring endorsed certificate for other node {}", + node_id); + continue; + } + + if (!endorsed_certificate.has_value()) + { + LOG_FAIL_FMT( + "[global] Endorsed cert for self ({}) has been deleted", self); + throw std::logic_error(fmt::format( + "Could not find endorsed node certificate for {}", self)); + } + + const auto new_endorsed_node_cert = endorsed_certificate.value(); + + LOG_INFO_FMT("[global] Accepting network connections"); + accept_network_tls_connections(new_endorsed_node_cert); + + if (is_member_frontend_open()) + { + // Also, automatically refresh self-signed node certificate, + // using the same validity period as the endorsed certificate. + // Note that this is only done when the certificate is renewed + // via proposal (i.e. when the member frontend is open), and not + // for the initial addition of the node (the self-signed + // certificate is output to disk then). + auto [valid_from, valid_to] = + ccf::crypto::make_verifier(new_endorsed_node_cert) + ->validity_period(); + LOG_INFO_FMT( + "[global] Member frontend is open, so refreshing self-signed " + "node cert"); + const auto new_self_signed_node_cert = create_self_signed_cert( + node_sign_kp, + config.node_certificate.subject_name, + subject_alt_names, + valid_from, + valid_to); + + ccf::crypto::Pem previous_self_signed_node_cert; + { + std::lock_guard cert_guard(node_certificates_lock); + previous_self_signed_node_cert = self_signed_node_cert; + self_signed_node_cert = new_self_signed_node_cert; } LOG_INFO_FMT( - "Found no ledger secrets for this node ({}) in global commit hook " - "for {} @ {}", - self, - network.secrets.get_name(), - hook_version); - })); + "[global] Previously:\n{}", previous_self_signed_node_cert.str()); + LOG_INFO_FMT("[global] Now:\n{}", new_self_signed_node_cert.str()); + + LOG_INFO_FMT("[global] Accepting node connections"); + accept_node_tls_connections(new_self_signed_node_cert); + } + else + { + const auto current_self_signed_node_cert = + get_self_signed_certificate(); + LOG_INFO_FMT("[global] Member frontend is NOT open"); + LOG_INFO_FMT( + "[global] Self-signed node cert remains:\n{}", + current_self_signed_node_cert.str()); + } + + LOG_INFO_FMT("[global] Opening members frontend"); + open_frontend_async(ActorsType::members); + } + } + + void on_service_global_commit( + ccf::kv::Version hook_version, const Service::Write& w) + { + if (!w.has_value()) + { + throw std::logic_error("Unexpected deletion in service value"); + } + + // Service open on historical service has no effect + auto hook_pubk_pem = ccf::crypto::public_key_pem_from_cert( + ccf::crypto::cert_pem_to_der(w->cert)); + auto current_pubk_pem = + ccf::crypto::make_ec_key_pair(network.identity->priv_key) + ->public_key_pem(); + if (hook_pubk_pem != current_pubk_pem) + { + LOG_TRACE_FMT( + "Ignoring historical service open at seqno {} for {}", + hook_version, + w->cert.str()); + return; + } + + LOG_INFO_FMT( + "Executing global hook for service table at {}, to service " + "status {}. Cert is:\n{}", + hook_version, + w->status, + w->cert.str()); + + network.identity->set_certificate(w->cert); + if (w->status == ServiceStatus::OPEN) + { + recovered_service_opening = RecoveredServiceOpening::Committed; + open_frontend_async(ActorsType::users); + + RINGBUFFER_WRITE_MESSAGE(::consensus::ledger_open, to_host); + LOG_INFO_FMT("Service open at seqno {}", hook_version); + } + } + + void setup_basic_hooks() + { + network.tables->set_map_hook( + network.secrets.get_name(), + Secrets::wrap_map_hook( + [this](ccf::kv::Version hook_version, const Secrets::Write& w) { + return on_secrets_local_commit(hook_version, w); + })); + + network.tables->set_global_hook( + network.secrets.get_name(), + Secrets::wrap_commit_hook( + [this](ccf::kv::Version hook_version, const Secrets::Write& w) { + on_secrets_global_commit(hook_version, w); + })); network.tables->set_global_hook( network.nodes.get_name(), Nodes::wrap_commit_hook( [this](ccf::kv::Version hook_version, const Nodes::Write& w) { - std::vector retired_committed_nodes; - for (const auto& [node_id, node_info] : w) - { - if (node_info.has_value() && node_info->retired_committed) - { - retired_committed_nodes.push_back(node_id); - } - } - consensus->set_retired_committed( - hook_version, retired_committed_nodes); + on_nodes_global_commit(hook_version, w); })); // Service-endorsed certificate is passed to history as early as _local_ @@ -3422,54 +3606,8 @@ namespace ccf NodeEndorsedCertificates::wrap_map_hook( [this]( ccf::kv::Version hook_version, - const NodeEndorsedCertificates::Write& w) - -> ccf::kv::ConsensusHookPtr { - LOG_INFO_FMT( - "[local] node_endorsed_certificates local hook at version {}, " - "with {} writes", - hook_version, - w.size()); - for (auto const& [node_id, endorsed_certificate] : w) - { - if (node_id != self) - { - LOG_INFO_FMT( - "[local] Ignoring endorsed certificate for other node {}", - node_id); - continue; - } - - if (!endorsed_certificate.has_value()) - { - LOG_FAIL_FMT( - "[local] Endorsed cert for self ({}) has been deleted", self); - throw std::logic_error(fmt::format( - "Could not find endorsed node certificate for {}", self)); - } - - const auto new_endorsed_node_cert = endorsed_certificate.value(); - std::optional previous_endorsed_node_cert; - { - std::lock_guard cert_guard(node_certificates_lock); - previous_endorsed_node_cert = endorsed_node_cert; - endorsed_node_cert = new_endorsed_node_cert; - } - - if (previous_endorsed_node_cert.has_value()) - { - LOG_INFO_FMT( - "[local] Previous endorsed node cert was:\n{}", - previous_endorsed_node_cert->str()); - } - - LOG_INFO_FMT( - "[local] Setting endorsed node cert to:\n{}", - new_endorsed_node_cert.str()); - history->set_endorsed_certificate(new_endorsed_node_cert); - n2n_channels->set_endorsed_node_cert(new_endorsed_node_cert); - } - - return {nullptr}; + const NodeEndorsedCertificates::Write& w) { + return on_node_endorsed_certificates_local_commit(hook_version, w); })); network.tables->set_global_hook( @@ -3478,127 +3616,14 @@ namespace ccf [this]( ccf::kv::Version hook_version, const NodeEndorsedCertificates::Write& w) { - LOG_INFO_FMT( - "[global] node_endorsed_certificates global hook at version {}, " - "with {} writes", - hook_version, - w.size()); - for (auto const& [node_id, endorsed_certificate] : w) - { - if (node_id != self) - { - LOG_INFO_FMT( - "[global] Ignoring endorsed certificate for other node {}", - node_id); - continue; - } - - if (!endorsed_certificate.has_value()) - { - LOG_FAIL_FMT( - "[global] Endorsed cert for self ({}) has been deleted", - self); - throw std::logic_error(fmt::format( - "Could not find endorsed node certificate for {}", self)); - } - - const auto new_endorsed_node_cert = endorsed_certificate.value(); - - LOG_INFO_FMT("[global] Accepting network connections"); - accept_network_tls_connections(new_endorsed_node_cert); - - if (is_member_frontend_open()) - { - // Also, automatically refresh self-signed node certificate, - // using the same validity period as the endorsed certificate. - // Note that this is only done when the certificate is renewed - // via proposal (i.e. when the member frontend is open), and not - // for the initial addition of the node (the self-signed - // certificate is output to disk then). - auto [valid_from, valid_to] = - ccf::crypto::make_verifier(new_endorsed_node_cert) - ->validity_period(); - LOG_INFO_FMT( - "[global] Member frontend is open, so refreshing self-signed " - "node cert"); - const auto new_self_signed_node_cert = create_self_signed_cert( - node_sign_kp, - config.node_certificate.subject_name, - subject_alt_names, - valid_from, - valid_to); - - ccf::crypto::Pem previous_self_signed_node_cert; - { - std::lock_guard cert_guard(node_certificates_lock); - previous_self_signed_node_cert = self_signed_node_cert; - self_signed_node_cert = new_self_signed_node_cert; - } - - LOG_INFO_FMT( - "[global] Previously:\n{}", - previous_self_signed_node_cert.str()); - LOG_INFO_FMT( - "[global] Now:\n{}", new_self_signed_node_cert.str()); - - LOG_INFO_FMT("[global] Accepting node connections"); - accept_node_tls_connections(new_self_signed_node_cert); - } - else - { - const auto current_self_signed_node_cert = - get_self_signed_certificate(); - LOG_INFO_FMT("[global] Member frontend is NOT open"); - LOG_INFO_FMT( - "[global] Self-signed node cert remains:\n{}", - current_self_signed_node_cert.str()); - } - - LOG_INFO_FMT("[global] Opening members frontend"); - open_frontend_async(ActorsType::members); - } + on_node_endorsed_certificates_global_commit(hook_version, w); })); network.tables->set_global_hook( network.service.get_name(), Service::wrap_commit_hook( [this](ccf::kv::Version hook_version, const Service::Write& w) { - if (!w.has_value()) - { - throw std::logic_error("Unexpected deletion in service value"); - } - - // Service open on historical service has no effect - auto hook_pubk_pem = ccf::crypto::public_key_pem_from_cert( - ccf::crypto::cert_pem_to_der(w->cert)); - auto current_pubk_pem = - ccf::crypto::make_ec_key_pair(network.identity->priv_key) - ->public_key_pem(); - if (hook_pubk_pem != current_pubk_pem) - { - LOG_TRACE_FMT( - "Ignoring historical service open at seqno {} for {}", - hook_version, - w->cert.str()); - return; - } - - LOG_INFO_FMT( - "Executing global hook for service table at {}, to service " - "status {}. Cert is:\n{}", - hook_version, - w->status, - w->cert.str()); - - network.identity->set_certificate(w->cert); - if (w->status == ServiceStatus::OPEN) - { - recovered_service_opening = RecoveredServiceOpening::Committed; - open_frontend_async(ActorsType::users); - - RINGBUFFER_WRITE_MESSAGE(::consensus::ledger_open, to_host); - LOG_INFO_FMT("Service open at seqno {}", hook_version); - } + on_service_global_commit(hook_version, w); })); }