diff --git a/Dockerfile.builder b/Dockerfile.builder index 3961afc0..5f5f2613 100644 --- a/Dockerfile.builder +++ b/Dockerfile.builder @@ -47,6 +47,11 @@ COPY --from=dependencies /root/.rustup /root/.rustup # Copy project source code COPY . ${PROJECT} +# Create minimal .git structure for build version generation +# (actual .git is excluded by .dockerignore for smaller image) +RUN mkdir -p ${PROJECT}/.git && \ + echo "${GIT_COMMIT}" > ${PROJECT}/.git/HEAD + # Build project RUN set -eux; \ export PATH="${HOME}/.cargo/bin:${PATH}"; \ diff --git a/example/config.yaml b/example/config.yaml index 1cc62c94..66f78f15 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -42,3 +42,11 @@ logging: - name: storage children: - name: block_storage + - name: blockchain + children: + - name: block_tree + - name: fork_choice + level: trace + children: + - name: stf + level: info \ No newline at end of file diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 01c593f1..a2fc0d91 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -4,35 +4,81 @@ # SPDX-License-Identifier: Apache-2.0 # +execute_process( + COMMAND git symbolic-ref -q HEAD + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_REF + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET +) + +if(GIT_REF) + set(GIT_REF_FILE "${CMAKE_SOURCE_DIR}/.git/${GIT_REF}") +else() + set(GIT_REF_FILE "${CMAKE_SOURCE_DIR}/.git/HEAD") +endif() + set(BUILD_VERSION_CPP "${CMAKE_BINARY_DIR}/generated/app/build_version.cpp") set(GET_VERSION_SCRIPT "${CMAKE_SOURCE_DIR}/scripts/get_version.sh") add_custom_command( OUTPUT ${BUILD_VERSION_CPP} - COMMAND echo "// Auto-generated file" > ${BUILD_VERSION_CPP} - COMMAND echo "#include " >> ${BUILD_VERSION_CPP} - COMMAND echo "namespace lean {" >> ${BUILD_VERSION_CPP} - COMMAND echo " const std::string &buildVersion() {" >> ${BUILD_VERSION_CPP} - COMMAND printf " static const std::string buildVersion(\"" >> ${BUILD_VERSION_CPP} - COMMAND ${GET_VERSION_SCRIPT} >> ${BUILD_VERSION_CPP} - COMMAND echo "\");" >> ${BUILD_VERSION_CPP} - COMMAND echo " return buildVersion;" >> ${BUILD_VERSION_CPP} - COMMAND echo " }" >> ${BUILD_VERSION_CPP} - COMMAND echo "}" >> ${BUILD_VERSION_CPP} + + COMMAND ${CMAKE_COMMAND} -E make_directory "$" + + COMMAND ${CMAKE_COMMAND} -E echo "// Auto-generated file" > "${BUILD_VERSION_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo "#include " >> "${BUILD_VERSION_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo "" >> "${BUILD_VERSION_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo "namespace lean {" >> "${BUILD_VERSION_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo " const std::string &buildVersion() {" >> "${BUILD_VERSION_CPP}" + COMMAND ${CMAKE_COMMAND} -E env sh -c "printf '%s' ' static const std::string buildVersion(\"' >> \"${BUILD_VERSION_CPP}\"" + + COMMAND ${CMAKE_COMMAND} -E env sh -c "\"${GET_VERSION_SCRIPT}\" >> \"${BUILD_VERSION_CPP}\"" + + COMMAND ${CMAKE_COMMAND} -E echo "\");" >> "${BUILD_VERSION_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo " return buildVersion;" >> "${BUILD_VERSION_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo " }" >> "${BUILD_VERSION_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo "} // namespace lean" >> "${BUILD_VERSION_CPP}" + COMMENT "Generate build_version.cpp" - DEPENDS ${GET_VERSION_SCRIPT} + DEPENDS + "${GET_VERSION_SCRIPT}" + "${GIT_REF_FILE}" VERBATIM ) add_library(build_version "${BUILD_VERSION_CPP}" ) -set(DEFAULT_LOGGING_YAML_CPP "${CMAKE_BINARY_DIR}/generated/app/default_logging_yaml.cpp") -file(READ "${CMAKE_SOURCE_DIR}/example/config.yaml" DEFAULT_LOGGING_YAML) -configure_file("${CMAKE_CURRENT_LIST_DIR}/default_logging_yaml.cpp.in" "${DEFAULT_LOGGING_YAML_CPP}") +set(DEFAULT_CONFIG_CPP "${CMAKE_BINARY_DIR}/generated/app/default_config.cpp") +set(DEFAULT_CONFIG_YAML "${CMAKE_SOURCE_DIR}/example/config.yaml") +add_custom_command( + OUTPUT "${DEFAULT_CONFIG_CPP}" + + COMMAND ${CMAKE_COMMAND} -E make_directory "$" + + COMMAND ${CMAKE_COMMAND} -E echo "// Auto-generated file" > "${DEFAULT_CONFIG_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo "#include \"app/default_config.hpp\"" >> "${DEFAULT_CONFIG_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo "" >> "${DEFAULT_CONFIG_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo "namespace lean {" >> "${DEFAULT_CONFIG_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo " const std::string &defaultConfigYaml() {" >> "${DEFAULT_CONFIG_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo " static const std::string defaultConfigYaml = R\"yaml(" >> "${DEFAULT_CONFIG_CPP}" + + COMMAND ${CMAKE_COMMAND} -E cat "${DEFAULT_CONFIG_YAML}" >> "${DEFAULT_CONFIG_CPP}" + + COMMAND ${CMAKE_COMMAND} -E echo "" >> "${DEFAULT_CONFIG_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo ")yaml\";" >> "${DEFAULT_CONFIG_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo " return defaultConfigYaml;" >> "${DEFAULT_CONFIG_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo " }" >> "${DEFAULT_CONFIG_CPP}" + COMMAND ${CMAKE_COMMAND} -E echo "} // namespace lean" >> "${DEFAULT_CONFIG_CPP}" + + DEPENDS "${DEFAULT_CONFIG_YAML}" + COMMENT "Generate default_config.cpp" + VERBATIM +) add_library(app_configuration SHARED configuration.cpp - "${DEFAULT_LOGGING_YAML_CPP}" + "${DEFAULT_CONFIG_CPP}" ) target_link_libraries(app_configuration Boost::boost diff --git a/src/app/configurator.cpp b/src/app/configurator.cpp index 0f9233b7..e9b6d054 100644 --- a/src/app/configurator.cpp +++ b/src/app/configurator.cpp @@ -26,7 +26,7 @@ #include "app/build_version.hpp" #include "app/configuration.hpp" -#include "app/default_logging_yaml.hpp" +#include "app/default_config.hpp" #include "app/validator_keys_manifest.hpp" #include "crypto/xmss/xmss_util.hpp" #include "log/formatters/filepath.hpp" @@ -161,9 +161,17 @@ namespace lean::app { namespace po = boost::program_options; namespace fs = std::filesystem; + // clang-format off + po::options_description options; - options.add_options()("help,h", "show help")("version,v", "show version")( - "config,c", po::value(), "config-file path"); + options.add_options() + ("help,h", "show help") + ("version,v", "show version") + ("config,c", po::value(), "config-file path") + ("log,l", po::value>(), "Sets a custom logging filter") + ; + + // clang-format on po::variables_map vm; @@ -211,6 +219,10 @@ namespace lean::app { } } + if (vm.contains("log")) { + logger_cli_args_ = vm["log"].as>(); + } + return false; } @@ -237,10 +249,10 @@ namespace lean::app { outcome::result Configurator::getLoggingConfig() { auto load_default = [&]() -> outcome::result { try { - return YAML::Load(defaultLoggingYaml())["logging"]; + return YAML::Load(defaultConfigYaml())["logging"]; } catch (const std::exception &e) { - file_errors_ << "E: Failed to load default logging config: " << e.what() - << "\n"; + file_errors_ << "E: Failed to load embedded default config: " + << e.what() << "\n"; return Error::ConfigFileParseFailed; } }; @@ -254,6 +266,7 @@ namespace lean::app { } return load_default(); } + outcome::result> Configurator::calculateConfig( qtils::SharedRef logger) { logger_ = std::move(logger); @@ -405,7 +418,8 @@ namespace lean::app { auto value = validator_keys_manifest.as(); config_->validator_keys_manifest_path_ = value; } else { - file_errors_ << "E: Value 'general.validator-keys-manifest' must be scalar\n"; + file_errors_ << "E: Value 'general.validator-keys-manifest' must " + "be scalar\n"; file_has_error_ = true; } } @@ -504,10 +518,11 @@ namespace lean::app { cli_values_map_, "xmss-sk", [&](const std::string &value) { config_->xmss_secret_key_path_ = value; }); - find_argument( - cli_values_map_, "validator-keys-manifest", [&](const std::string &value) { - config_->validator_keys_manifest_path_ = value; - }); + find_argument(cli_values_map_, + "validator-keys-manifest", + [&](const std::string &value) { + config_->validator_keys_manifest_path_ = value; + }); if (fail) { return Error::CliArgsParseFailed; } @@ -610,11 +625,13 @@ namespace lean::app { // Validate and load XMSS keys (mandatory) if (config_->xmss_public_key_path_.empty()) { - SL_ERROR(logger_, "The '--xmss-pk' (XMSS public key) path must be provided"); + SL_ERROR(logger_, + "The '--xmss-pk' (XMSS public key) path must be provided"); return Error::InvalidValue; } if (config_->xmss_secret_key_path_.empty()) { - SL_ERROR(logger_, "The '--xmss-sk' (XMSS secret key) path must be provided"); + SL_ERROR(logger_, + "The '--xmss-sk' (XMSS secret key) path must be provided"); return Error::InvalidValue; } @@ -637,10 +654,10 @@ namespace lean::app { } // Load XMSS keypair from JSON files - OUTCOME_TRY(keypair, crypto::xmss::loadKeypairFromJson( - config_->xmss_secret_key_path_, - config_->xmss_public_key_path_ - )); + OUTCOME_TRY( + keypair, + crypto::xmss::loadKeypairFromJson(config_->xmss_secret_key_path_, + config_->xmss_public_key_path_)); config_->xmss_keypair_ = std::move(keypair); SL_INFO(logger_, "Loaded XMSS keypair from:"); SL_INFO(logger_, " Public key: {}", config_->xmss_public_key_path_); @@ -648,15 +665,17 @@ namespace lean::app { // Load validator keys manifest (mandatory) if (config_->validator_keys_manifest_path_.empty()) { - SL_ERROR(logger_, "The '--validator-keys-manifest' path must be provided"); + SL_ERROR(logger_, + "The '--validator-keys-manifest' path must be provided"); return Error::InvalidValue; } - config_->validator_keys_manifest_path_ = - resolve_relative(config_->validator_keys_manifest_path_, "validator-keys-manifest"); + config_->validator_keys_manifest_path_ = resolve_relative( + config_->validator_keys_manifest_path_, "validator-keys-manifest"); if (not is_regular_file(config_->validator_keys_manifest_path_)) { SL_ERROR(logger_, - "The 'validator-keys-manifest' file does not exist or is not a file: {}", + "The 'validator-keys-manifest' file does not exist or is not a " + "file: {}", config_->validator_keys_manifest_path_); return Error::InvalidValue; } diff --git a/src/app/configurator.hpp b/src/app/configurator.hpp index 8246f235..4d58ba50 100644 --- a/src/app/configurator.hpp +++ b/src/app/configurator.hpp @@ -50,6 +50,9 @@ namespace lean::app { outcome::result step2(); outcome::result getLoggingConfig(); + std::vector getLoggingCliArgs() { + return logger_cli_args_; + } outcome::result> calculateConfig( qtils::SharedRef logger); @@ -70,6 +73,7 @@ namespace lean::app { bool file_has_warn_ = false; bool file_has_error_ = false; std::ostringstream file_errors_; + std::vector logger_cli_args_; boost::program_options::options_description cli_options_; boost::program_options::variables_map cli_values_map_; diff --git a/src/app/default_logging_yaml.hpp b/src/app/default_config.hpp similarity index 68% rename from src/app/default_logging_yaml.hpp rename to src/app/default_config.hpp index ec971545..8333042e 100644 --- a/src/app/default_logging_yaml.hpp +++ b/src/app/default_config.hpp @@ -10,7 +10,7 @@ namespace lean { /** - * YAML to read default logging config. + * YAML to read default config. */ - const std::string &defaultLoggingYaml(); + const std::string &defaultConfigYaml(); } // namespace lean diff --git a/src/app/default_logging_yaml.cpp.in b/src/app/default_logging_yaml.cpp.in deleted file mode 100644 index b3b9f4af..00000000 --- a/src/app/default_logging_yaml.cpp.in +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright Quadrivium LLC - * All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "app/default_logging_yaml.hpp" - -namespace lean { - const std::string &defaultLoggingYaml() { - static std::string yaml = R"yaml( -@DEFAULT_LOGGING_YAML@ -)yaml"; - return yaml; - } -} // namespace lean diff --git a/src/app/impl/chain_spec_impl.cpp b/src/app/impl/chain_spec_impl.cpp index 7d63472f..6fdc010a 100644 --- a/src/app/impl/chain_spec_impl.cpp +++ b/src/app/impl/chain_spec_impl.cpp @@ -83,11 +83,6 @@ namespace lean::app { auto multiaddr = entry.enr.connectAddress(); bootnode_infos.emplace_back(std::move(multiaddr), std::move(peer_id)); - SL_INFO(log_, - "Added boot node: {} -> peer={}, address={}", - entry.raw, - bootnode_infos.back().peer_id, - bootnode_infos.back().address.getStringAddress()); } catch (const std::exception &e) { SL_WARN(log_, "Failed to extract peer info from ENR '{}': {}", diff --git a/src/app/impl/timeline_impl.cpp b/src/app/impl/timeline_impl.cpp index 695689aa..2d0c0d2e 100644 --- a/src/app/impl/timeline_impl.cpp +++ b/src/app/impl/timeline_impl.cpp @@ -7,12 +7,15 @@ #include "timeline_impl.hpp" +#include #include #include "app/state_manager.hpp" +#include "blockchain/block_tree.hpp" #include "blockchain/genesis_config.hpp" #include "clock/clock.hpp" #include "log/logger.hpp" +#include "modules/shared/networking_types.tmp.hpp" #include "modules/shared/prodution_types.tmp.hpp" #include "se/impl/subscription_manager.hpp" #include "se/subscription.hpp" @@ -26,12 +29,14 @@ namespace lean::app { qtils::SharedRef state_manager, qtils::SharedRef se_manager, qtils::SharedRef clock, - qtils::SharedRef config) + qtils::SharedRef config, + qtils::SharedRef block_tree) : logger_(logsys->getLogger("Timeline", "application")), state_manager_(std::move(state_manager)), config_(std::move(config)), clock_(std::move(clock)), - se_manager_(std::move(se_manager)) { + se_manager_(std::move(se_manager)), + block_tree_(std::move(block_tree)) { state_manager_->takeControl(*this); } @@ -46,6 +51,17 @@ namespace lean::app { std::shared_ptr msg) { on_slot_started(std::move(msg)); }); + on_peers_total_count_updated_ = se::SubscriberCreator< + qtils::Empty, + std::shared_ptr>:: + create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [this]( + auto &, + std::shared_ptr msg) { + connected_peers_ = msg->count; + }); } void TimelineImpl::start() { @@ -76,6 +92,51 @@ namespace lean::app { void TimelineImpl::on_slot_started( std::shared_ptr msg) { + auto head = block_tree_->bestBlock(); + auto finalized = block_tree_->lastFinalized(); + auto justified = block_tree_->getLatestJustified(); + auto head_block_header_res = block_tree_->getBlockHeader(head.hash); + + BlockHash parent_root{}; + StateRoot state_root{}; + + if (head_block_header_res) { + parent_root = head_block_header_res.value().parent_root; + state_root = head_block_header_res.value().state_root; + } + + fmt::println( + std::cerr, + "+===============================================================+"); + fmt::println(std::cerr, + " CHAIN STATUS: Current Slot: {} | Head Slot: {}", + msg->slot, + head.slot); + fmt::println(std::cerr, + "+---------------------------------------------------------------+"); + fmt::println(std::cerr, " Connected Peers: {}", + connected_peers_.load()); + fmt::println(std::cerr, + "+---------------------------------------------------------------+"); + fmt::println(std::cerr, " Head Block Root: 0x{}", head.hash.toHex()); + fmt::println(std::cerr, " Parent Block Root: 0x{}", parent_root.toHex()); + fmt::println(std::cerr, " State Root: 0x{}", state_root.toHex()); + fmt::println( + std::cerr, + "+---------------------------------------------------------------+"); + fmt::println(std::cerr, + " Latest Justified: Slot {:>6} | Root: 0x{}", + justified.slot, + justified.root.toHex()); + fmt::println(std::cerr, + " Latest Finalized: Slot {:>6} | Root: 0x{}", + finalized.slot, + finalized.hash.toHex()); + fmt::println( + std::cerr, + "+===============================================================+"); + + SL_INFO(logger_, "⚡ Slot {} started", msg->slot); if (stopped_) [[unlikely]] { SL_INFO(logger_, "Timeline is stopped on slot {}", msg->slot); return; @@ -87,8 +148,6 @@ namespace lean::app { auto time_to_next_slot = config_->config.genesis_time * 1000 + SLOT_DURATION_MS * next_slot - now; - SL_INFO(logger_, "Next slot is {} in {}ms", next_slot, time_to_next_slot); - const auto slot_start_abs = config_->config.genesis_time * 1000 + SLOT_DURATION_MS * msg->slot; // in milliseconds diff --git a/src/app/impl/timeline_impl.hpp b/src/app/impl/timeline_impl.hpp index 37c844fd..4a3ae6d4 100644 --- a/src/app/impl/timeline_impl.hpp +++ b/src/app/impl/timeline_impl.hpp @@ -14,6 +14,7 @@ namespace lean::messages { struct SlotStarted; + struct PeersTotalCountMessage; } namespace lean { struct GenesisConfig; @@ -21,6 +22,9 @@ namespace lean { namespace lean::log { class LoggingSystem; } +namespace lean::blockchain { + class BlockTree; +} namespace lean::clock { class SystemClock; } @@ -40,7 +44,8 @@ namespace lean::app { qtils::SharedRef state_manager, qtils::SharedRef se_manager, qtils::SharedRef clock, - qtils::SharedRef config); + qtils::SharedRef config, + qtils::SharedRef block_tree); void prepare(); void start(); @@ -54,6 +59,9 @@ namespace lean::app { qtils::SharedRef config_; qtils::SharedRef clock_; qtils::SharedRef se_manager_; + qtils::SharedRef block_tree_; + + std::atomic connected_peers_{0}; bool stopped_ = false; @@ -61,6 +69,10 @@ namespace lean::app { BaseSubscriber>> on_slot_started_; + std::shared_ptr< + BaseSubscriber>> + on_peers_total_count_updated_; }; } // namespace lean::app diff --git a/src/blockchain/block_tree.hpp b/src/blockchain/block_tree.hpp index dea750a1..abc11c35 100644 --- a/src/blockchain/block_tree.hpp +++ b/src/blockchain/block_tree.hpp @@ -14,6 +14,7 @@ namespace lean { struct BlockBody; struct SignedBlockWithAttestation; struct StatusMessage; + struct Checkpoint; } // namespace lean namespace lean::blockchain { @@ -154,6 +155,12 @@ namespace lean::blockchain { */ [[nodiscard]] virtual BlockIndex lastFinalized() const = 0; + /** + * Get the latest justified checkpoint + * @return checkpoint + */ + [[nodiscard]] virtual Checkpoint getLatestJustified() const = 0; + /** * Get `SignedBlockWithAttestation` for * "/leanconsensus/req/blocks_by_root/1/ssz_snappy" protocol. diff --git a/src/blockchain/fork_choice.cpp b/src/blockchain/fork_choice.cpp index 46a1c5b9..73076325 100644 --- a/src/blockchain/fork_choice.cpp +++ b/src/blockchain/fork_choice.cpp @@ -260,7 +260,7 @@ namespace lean { auto &data = signed_attestation.message.data; SL_TRACE(logger_, - "Validating attestation for target {}, source {}", + "Validating attestation for target={}, source={}", data.target, data.source); auto timer = metrics_->fc_attestation_validation_time_seconds()->timer(); @@ -312,10 +312,33 @@ namespace lean { const SignedAttestation &signed_attestation, bool is_from_block) { // First, ensure the attestation is structurally and temporally valid. auto source = is_from_block ? "block" : "gossip"; + + // Extract node id + auto node_id_opt = validator_registry_->nodeIdByIndex( + signed_attestation.message.validator_id); + if (not node_id_opt.has_value()) { + SL_WARN(logger_, + "Received attestation from unknown validator index {}", + signed_attestation.message.validator_id); + } + if (auto res = validateAttestation(signed_attestation); res.has_value()) { metrics_->fc_attestations_valid_total({{"source", source}})->inc(); + SL_DEBUG(logger_, + "⚙️ Processing valid attestation from validator {} for " + "target={}, source={}", + node_id_opt ? node_id_opt.value() : "unknown", + signed_attestation.message.data.target, + signed_attestation.message.data.source); } else { metrics_->fc_attestations_invalid_total({{"source", source}})->inc(); + SL_WARN(logger_, + "❌ Invalid attestation from validator {} for target={}, " + "source={}: {}", + node_id_opt ? node_id_opt.value() : "unknown", + signed_attestation.message.data.target, + signed_attestation.message.data.source, + res.error()); return res; } @@ -479,6 +502,9 @@ namespace lean { return false; } } + SL_TRACE(logger_, + "All block signatures are valid in block {}", + block.slotHash()); return true; } @@ -523,6 +549,10 @@ namespace lean { // If post-state has a higher finalized checkpoint, update it to the store. if (post_state.latest_finalized.slot > latest_finalized_.slot) { + SL_INFO(logger_, + "🔒 Finalized block={:0xx}, slot={}", + post_state.latest_finalized.root, + post_state.latest_finalized.slot); latest_finalized_ = post_state.latest_finalized; } @@ -588,10 +618,10 @@ namespace lean { } if (time_ % INTERVALS_PER_SLOT == 0) { // Slot start - SL_INFO(logger_, - "Slot {} started with time {}", - current_slot, - time_ * SECONDS_PER_INTERVAL); + SL_DEBUG(logger_, + "Slot {} started with time {}", + current_slot, + time_ * SECONDS_PER_INTERVAL); auto producer_index = current_slot % validator_count; auto is_producer = validator_registry_->currentValidatorIndices().contains( @@ -611,7 +641,7 @@ namespace lean { auto &new_signed_block = res.value(); SL_INFO(logger_, - "Produced block {} with parent {} state {}", + "👷 Produced block={} with parent={:0xx} state={:0xx}", new_signed_block.message.block.slotHash(), new_signed_block.message.block.parent_root, new_signed_block.message.block.state_root); @@ -629,12 +659,11 @@ namespace lean { metrics_->fc_head_slot()->set(head_slot.value()); Checkpoint head{.root = head_root, .slot = head_slot.value()}; auto target = getAttestationTarget(); - SL_INFO(logger_, - "For slot {}: head is {}, target is {}, source is {}", - current_slot, - head, - target, - latest_justified_); + + SL_INFO(logger_, "🔷 Head={}", head); + SL_INFO(logger_, "🎯 Target={}", target); + SL_INFO(logger_, "📌 Source={}", latest_justified_); + for (auto validator_index : validator_registry_->currentValidatorIndices()) { if (isProposer(validator_index, current_slot, validator_count)) { @@ -664,24 +693,24 @@ namespace lean { res.error()); continue; } - SL_INFO(logger_, - "Produced vote for target {}", - signed_attestation.message.data.target); + SL_DEBUG(logger_, + "Produced vote for target={}", + signed_attestation.message.data.target); result.emplace_back(std::move(signed_attestation)); } } else if (time_ % INTERVALS_PER_SLOT == 2) { // Interval two actions - SL_INFO(logger_, - "Interval two of slot {} at time {}", - current_slot, - time_ * SECONDS_PER_INTERVAL); + SL_DEBUG(logger_, + "Interval two of slot {} at time {}", + current_slot, + time_ * SECONDS_PER_INTERVAL); updateSafeTarget(); } else if (time_ % INTERVALS_PER_SLOT == 3) { // Interval three actions - SL_INFO(logger_, - "Interval three of slot {} at time {}", - current_slot, - time_ * SECONDS_PER_INTERVAL); + SL_DEBUG(logger_, + "Interval three of slot {} at time {}", + current_slot, + time_ * SECONDS_PER_INTERVAL); acceptNewAttestations(); } time_ += 1; @@ -818,7 +847,7 @@ namespace lean { qtils::SharedRef validator_registry, qtils::SharedRef validator_keys_manifest, qtils::SharedRef xmss_provider) - : stf_(metrics), + : stf_(metrics, logging_system->getLogger("STF", "stf")), validator_registry_(validator_registry), validator_keys_manifest_(validator_keys_manifest), logger_( @@ -844,15 +873,17 @@ namespace lean { SignedBlockWithAttestation{ .message = {.block = std::move(anchor_block)}, }); - SL_INFO( - logger_, "Anchor block {} at slot {}", anchor_root, anchor_block.slot); + SL_INFO(logger_, + "Anchor block={:xx} at slot {}", + anchor_root, + anchor_block.slot); states_.emplace(anchor_root, anchor_state); for (auto xmss_pubkey : validator_keys_manifest_->getAllXmssPubkeys()) { - SL_INFO(logger_, "Validator pubkey: {}", xmss_pubkey.toHex()); + SL_DEBUG(logger_, "Validator pubkey={}", xmss_pubkey.toHex()); } SL_INFO( logger_, - "Our pubkey: {}", + "🔑 Our pubkey={}", validator_keys_manifest_->currentNodeXmssKeypair().public_key.toHex()); } // Test constructor implementation @@ -873,7 +904,7 @@ namespace lean { qtils::SharedRef validator_registry, qtils::SharedRef validator_keys_manifest, qtils::SharedRef xmss_provider) - : stf_(metrics), + : stf_(metrics, logging_system->getLogger("STF", "stf")), time_(now_sec / SECONDS_PER_INTERVAL), logger_( logging_system->getLogger("ForkChoiceStore", "fork_choice_store")), diff --git a/src/blockchain/impl/block_tree_impl.cpp b/src/blockchain/impl/block_tree_impl.cpp index b92b4619..f783a8c1 100644 --- a/src/blockchain/impl/block_tree_impl.cpp +++ b/src/blockchain/impl/block_tree_impl.cpp @@ -770,6 +770,15 @@ namespace lean::blockchain { [&](const BlockTreeData &p) { return getLastFinalizedNoLock(p); }); } + Checkpoint BlockTreeImpl::getLatestJustified() const { + return block_tree_data_.sharedAccess([&](const BlockTreeData &p) { + auto finalized = getLastFinalizedNoLock(p); + // For now, return finalized as justified since we don't track separate + // justification in basic BlockTreeImpl yet + return Checkpoint{.root = finalized.hash, .slot = finalized.slot}; + }); + } + outcome::result> BlockTreeImpl::tryGetSignedBlock(const BlockHash block_hash) const { auto header_res = getBlockHeader(block_hash); diff --git a/src/blockchain/impl/block_tree_impl.hpp b/src/blockchain/impl/block_tree_impl.hpp index 307e3dcb..ad76d959 100644 --- a/src/blockchain/impl/block_tree_impl.hpp +++ b/src/blockchain/impl/block_tree_impl.hpp @@ -108,6 +108,8 @@ namespace lean::blockchain { BlockIndex lastFinalized() const override; + Checkpoint getLatestJustified() const override; + outcome::result> tryGetSignedBlock(const BlockHash block_hash) const override; void import(std::vector blocks) override; diff --git a/src/blockchain/impl/fc_block_tree.cpp b/src/blockchain/impl/fc_block_tree.cpp index 8fd212bb..83f3bec1 100644 --- a/src/blockchain/impl/fc_block_tree.cpp +++ b/src/blockchain/impl/fc_block_tree.cpp @@ -22,7 +22,12 @@ namespace lean::blockchain { outcome::result FCBlockTree::getBlockHeader( const BlockHash &block_hash) const { - throw std::runtime_error("FCBlockTree::getBlockHeader()"); + const auto &blocks = fork_choice_store_->getBlocks(); + auto it = blocks.find(block_hash); + if (blocks.end() == it) { + return BlockTreeError::HEADER_NOT_FOUND; + } + return it->second.message.block.getHeader(); } outcome::result> FCBlockTree::tryGetBlockHeader( @@ -104,6 +109,10 @@ namespace lean::blockchain { return BlockIndex{.slot = finalized.slot, .hash = finalized.root}; } + Checkpoint FCBlockTree::getLatestJustified() const { + return fork_choice_store_->getLatestJustified(); + } + outcome::result> FCBlockTree::tryGetSignedBlock(const BlockHash block_hash) const { auto &blocks = fork_choice_store_->getBlocks(); diff --git a/src/blockchain/impl/fc_block_tree.hpp b/src/blockchain/impl/fc_block_tree.hpp index 5e11d3c9..091aa65a 100644 --- a/src/blockchain/impl/fc_block_tree.hpp +++ b/src/blockchain/impl/fc_block_tree.hpp @@ -69,6 +69,8 @@ namespace lean::blockchain { BlockIndex lastFinalized() const override; + Checkpoint getLatestJustified() const override; + outcome::result> tryGetSignedBlock(const BlockHash block_hash) const override; void import(std::vector blocks) override; diff --git a/src/blockchain/state_transition_function.cpp b/src/blockchain/state_transition_function.cpp index 468a170c..a129cc1e 100644 --- a/src/blockchain/state_transition_function.cpp +++ b/src/blockchain/state_transition_function.cpp @@ -15,8 +15,8 @@ namespace lean { - STF::STF(qtils::SharedRef metrics) - : metrics_(std::move(metrics)) {} + STF::STF(qtils::SharedRef metrics, log::Logger logger) + : metrics_(std::move(metrics)), log_(std::move(logger)) {} inline bool getBit(const std::vector &bits, size_t i) { return i < bits.size() and bits.at(i); diff --git a/src/blockchain/state_transition_function.hpp b/src/blockchain/state_transition_function.hpp index 4dee375a..1658d898 100644 --- a/src/blockchain/state_transition_function.hpp +++ b/src/blockchain/state_transition_function.hpp @@ -10,6 +10,7 @@ #include #include +#include "log/logger.hpp" #include "app/impl/chain_spec_impl.hpp" #include "app/validator_keys_manifest.hpp" #include "blockchain/validator_registry.hpp" @@ -61,7 +62,7 @@ namespace lean { abort(); } - explicit STF(qtils::SharedRef metrics); + explicit STF(qtils::SharedRef metrics, log::Logger logger); static AnchorState generateGenesisState( const Config &config, @@ -90,5 +91,6 @@ namespace lean { private: qtils::SharedRef metrics_; + log::Logger log_; }; } // namespace lean diff --git a/src/executable/lean_node.cpp b/src/executable/lean_node.cpp index 7ac932dc..d993c1b7 100644 --- a/src/executable/lean_node.cpp +++ b/src/executable/lean_node.cpp @@ -177,6 +177,18 @@ int main(int argc, const char **argv, const char **env) { std::make_shared(std::move(logging_system)); }); + { // Tune logging by CLI arguments + auto tune_result = logging_system->tuneLoggingSystem( + app_configurator->getLoggingCliArgs()); + if (not tune_result.message.empty()) { + (tune_result.has_error ? std::cerr : std::cout) + << tune_result.message << '\n'; + } + if (tune_result.has_error) { + return EXIT_FAILURE; + } + } + // Parse remaining args if (auto res = app_configurator->step2(); res.has_value()) { if (res.value()) { diff --git a/src/injector/node_injector.cpp b/src/injector/node_injector.cpp index 17ae7e06..fb41035b 100644 --- a/src/injector/node_injector.cpp +++ b/src/injector/node_injector.cpp @@ -101,8 +101,8 @@ namespace { di::bind.to(), di::bind.to(), di::bind.to(), - di::bind.to(), di::bind.to(), + di::bind.to(), di::bind.to(), di::bind.to(), di::bind.to(), diff --git a/src/loaders/impl/networking_loader.hpp b/src/loaders/impl/networking_loader.hpp index 4e45106b..3865b524 100644 --- a/src/loaders/impl/networking_loader.hpp +++ b/src/loaders/impl/networking_loader.hpp @@ -144,6 +144,12 @@ namespace lean::loaders { se_manager_->notify(lean::EventTypes::PeerDisconnected, msg); } + void dispatch_peers_total_count_updated( + std::shared_ptr msg) override { + SL_TRACE(logger_, "Dispatch PeersTotalCountUpdated; count={}", msg->count); + se_manager_->notify(lean::EventTypes::PeersTotalCountUpdated, msg); + } + void dispatchStatusMessageReceived( std::shared_ptr message) override { diff --git a/src/log/formatters/block_index_ref.hpp b/src/log/formatters/block_index_ref.hpp index a8678597..7aa7ef97 100644 --- a/src/log/formatters/block_index_ref.hpp +++ b/src/log/formatters/block_index_ref.hpp @@ -19,7 +19,7 @@ namespace lean { template <> struct fmt::formatter { // Presentation format - bool long_form = false; + bool long_form = true; // Parses format specifications of the form ['s' | 'l']. constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { diff --git a/src/log/logger.cpp b/src/log/logger.cpp index 9becdae9..7afb4acb 100644 --- a/src/log/logger.cpp +++ b/src/log/logger.cpp @@ -60,46 +60,90 @@ namespace lean::log { std::shared_ptr logging_system) : logging_system_(logging_system) {} - void LoggingSystem::tuneLoggingSystem(const std::vector &cfg) { + soralog::Configurator::Result LoggingSystem::tuneLoggingSystem( + const std::vector &cfg) { + auto default_group_set = false; + + soralog::Configurator::Result result; if (cfg.empty()) { - return; + return result; } for (auto &chunk : cfg) { - if (auto res = str2lvl(chunk); res.has_value()) { - auto level = res.value(); - logging_system_->setLevelOfGroup(lean::log::defaultGroupName, level); + if (chunk.empty()) { + result.has_error = true; + result.message += "E: Empty arg\n"; continue; } - std::istringstream iss2(chunk); + std::istringstream iss1(chunk); + std::string piece; - std::string group_name; - if (not std::getline(iss2, group_name, '=')) { - std::cerr << "Can't read group"; - } - if (not logging_system_->getGroup(group_name)) { - std::cerr << "Unknown group: " << group_name - << std::endl; // NOLINT(performance-avoid-endl) - continue; - } + while (std::getline(iss1, piece, ',')) { + if (piece.empty()) { + continue; + } - std::string level_string; - if (not std::getline(iss2, level_string)) { - std::cerr << "Can't read level for group '" << group_name << "'" - << std::endl; // NOLINT(performance-avoid-endl) - continue; - } - auto res = str2lvl(level_string); - if (not res.has_value()) { - std::cerr << "Invalid level: " << level_string - << std::endl; // NOLINT(performance-avoid-endl) - continue; + std::size_t pos = piece.find('='); + if (pos == std::string::npos) { + auto res = str2lvl(piece); + if (res.has_value()) { + auto level = res.value(); + if (default_group_set) { + result.has_warning = true; + result.message += + "W: Level of default group was set several times; " + "last time to '" + + piece + "'\n"; + } else { + result.message += + "I: Level of default group was set to '" + piece + "'\n"; + } + logging_system_->setLevelOfGroup(lean::log::defaultGroupName, + level); + default_group_set = true; + continue; + } + result.has_error = true; + result.message += + "E: Invalid level of default group: " + piece + "\n"; + continue; + } + + std::istringstream iss2(piece); + + std::string group_name; + std::getline(iss2, group_name, '='); + + std::string level_string; + std::getline(iss2, level_string); + + if (not logging_system_->getGroup(group_name)) { + result.has_warning = true; + result.message += "W: Unknown group: " + group_name + "\n"; + continue; + } + + auto res = str2lvl(level_string); + if (not res.has_value()) { + result.has_error = true; + result.message += "E: Invalid level of group '" + group_name + + "': " + level_string + "\n"; + continue; + } + auto level = res.value(); + + logging_system_->setLevelOfGroup(group_name, level); } - auto level = res.value(); + } - logging_system_->setLevelOfGroup(group_name, level); + if (result.has_error or result.has_warning) { + result.message = + "I: Some problems are found during tuning of logging system by CLI " + "args:\n" + + result.message; } + return result; } } // namespace lean::log diff --git a/src/log/logger.hpp b/src/log/logger.hpp index f30d902f..cf085503 100644 --- a/src/log/logger.hpp +++ b/src/log/logger.hpp @@ -39,7 +39,8 @@ namespace lean::log { LoggingSystem(std::shared_ptr logging_system); - void tuneLoggingSystem(const std::vector &cfg); + soralog::Configurator::Result tuneLoggingSystem( + const std::vector &args); void doLogRotate() const { logging_system_->callRotateForAllSinks(); diff --git a/src/modules/networking/interfaces.hpp b/src/modules/networking/interfaces.hpp index 8d15fdfc..f9dccd50 100644 --- a/src/modules/networking/interfaces.hpp +++ b/src/modules/networking/interfaces.hpp @@ -19,6 +19,9 @@ namespace lean::modules { virtual void dispatch_peer_disconnected( std::shared_ptr msg) = 0; + virtual void dispatch_peers_total_count_updated( + std::shared_ptr msg) = 0; + virtual void dispatchStatusMessageReceived( std::shared_ptr message) = 0; virtual void dispatchSignedVoteReceived( diff --git a/src/modules/networking/networking.cpp b/src/modules/networking/networking.cpp index ce4634a3..d36bb520 100644 --- a/src/modules/networking/networking.cpp +++ b/src/modules/networking/networking.cpp @@ -112,7 +112,7 @@ namespace lean::modules { std::make_shared(nullptr)}; auto peer_id = identity_manager.getId(); - SL_INFO(logger_, "Networking loaded with PeerId {}", peer_id.toBase58()); + SL_INFO(logger_, "Networking loaded with PeerId={}", peer_id.toBase58()); libp2p::protocol::gossip::Config gossip_config; gossip_config.validation_mode = @@ -220,8 +220,7 @@ namespace lean::modules { if (result.has_value()) { SL_DEBUG(logger_, - "Added bootnode: peer={}, address={}", - bootnode.peer_id, + "Added bootnode, address={}", bootnode.address.getStringAddress()); } else { SL_WARN(logger_, @@ -308,7 +307,7 @@ namespace lean::modules { libp2p::peer::ttl::kRecentlyConnected); not result.has_value()) { SL_WARN(self->logger_, - "Failed to add addresses for peer {}: {}", + "Failed to add addresses for peer={}: {}", peer_id, result.error()); } @@ -413,23 +412,28 @@ namespace lean::modules { gossip_votes_topic_ = gossipSubscribe( "attestation", [weak_self{weak_from_this()}](SignedAttestation &&signed_attestation, - std::optional) { + std::optional peer_id) { auto self = weak_self.lock(); if (not self) { return; } + + SL_DEBUG(self->logger_, + "Received vote for target={} 🗳️ from peer={} 👤 " + "validator_id={} ✅", + signed_attestation.message.data.target, + peer_id.has_value() ? peer_id->toBase58() : "unknown", + signed_attestation.message.validator_id); + auto res = self->fork_choice_store_->onAttestation(signed_attestation, false); if (not res.has_value()) { SL_WARN(self->logger_, - "Error processing vote for target {}: {}", + "Error processing vote for target={}: {}", signed_attestation.message.data.target, res.error()); return; } - SL_DEBUG(self->logger_, - "Received vote for target {}", - signed_attestation.message.data.target); }); io_thread_.emplace([io_context{io_context_}] { @@ -445,6 +449,11 @@ namespace lean::modules { void NetworkingImpl::onSendSignedBlock( std::shared_ptr message) { boost::asio::post(*io_context_, [self{shared_from_this()}, message] { + auto slot_hash = message->notification.message.block.slotHash(); + SL_DEBUG(self->logger_, + "📣 Gossiped block in slot {} hash={:0xx} 🔗", + slot_hash.slot, + slot_hash.hash); self->gossip_blocks_topic_->publish( encodeSszSnappy(message->notification)); }); @@ -453,6 +462,9 @@ namespace lean::modules { void NetworkingImpl::onSendSignedVote( std::shared_ptr message) { boost::asio::post(*io_context_, [self{shared_from_this()}, message] { + SL_DEBUG(self->logger_, + "📣 Gossiped vote for target={} 🗳️", + message->notification.message.data.target); self->gossip_votes_topic_->publish( encodeSszSnappy(message->notification)); }); @@ -523,11 +535,14 @@ namespace lean::modules { std::optional from_peer, SignedBlockWithAttestation &&signed_block_with_attestation) { auto slot_hash = signed_block_with_attestation.message.block.slotHash(); - SL_INFO(logger_, - "receiveBlock slot {} hash {} parent {}", - slot_hash.slot, - slot_hash.hash, - signed_block_with_attestation.message.block.parent_root); + SL_DEBUG(logger_, + "Received block slot {} hash={:0xx} parent={:0xx} from peer={}", + slot_hash.slot, + slot_hash.hash, + signed_block_with_attestation.message.block.parent_root, + from_peer.has_value() ? from_peer->toBase58() : "unknown"); + + // Remove function for cached children auto remove = [&](auto f) { std::vector queue{slot_hash.hash}; while (not queue.empty()) { @@ -559,6 +574,8 @@ namespace lean::modules { if (block_tree_->has(parent_hash)) { std::vector blocks{ std::move(signed_block_with_attestation)}; + + // Import all cached children remove([&](const BlockHash &block_hash) { blocks.emplace_back(block_cache_.extract(block_hash).mapped()); }); @@ -566,15 +583,14 @@ namespace lean::modules { auto res = fork_choice_store_->onBlock(block); if (not res.has_value()) { SL_WARN(logger_, - "Error importing block {}: {}", + "❌ Error importing block={}: {}", block.message.block.slotHash(), res.error()); break; } } SL_INFO(logger_, - "receiveBlock {} => import {}", - slot_hash.slot, + "✅ Imported blocks: {}", fmt::join(blocks | std::views::transform( [](const SignedBlockWithAttestation &block) { @@ -653,8 +669,8 @@ namespace lean::modules { auto &state = self->peer_states_.at(peer_info.id); if (not r.has_value()) { SL_WARN(self->logger_, - "connect {} error: {}", - peer_info.id, + "connect={} error: {}", + peer_info.id.toBase58(), r.error()); if (auto *connecting = std::get_if(&state.state)) { @@ -670,6 +686,9 @@ namespace lean::modules { } void NetworkingImpl::updateMetricConnectedPeerCount() { - metrics_->connected_peer_count()->set(host_->getConnectedPeerCount()); + auto count = host_->getConnectedPeerCount(); + metrics_->connected_peer_count()->set(count); + loader_.dispatch_peers_total_count_updated( + std::make_shared(count)); } } // namespace lean::modules diff --git a/src/modules/shared/networking_types.tmp.hpp b/src/modules/shared/networking_types.tmp.hpp index f54c9ecc..7842d35a 100644 --- a/src/modules/shared/networking_types.tmp.hpp +++ b/src/modules/shared/networking_types.tmp.hpp @@ -40,6 +40,10 @@ namespace lean::messages { // reason? }; + struct PeersTotalCountMessage { + size_t count; + }; + using StatusMessageReceived = NotificationReceived; using SendSignedBlock = BroadcastNotification; diff --git a/src/se/subscription_fwd.hpp b/src/se/subscription_fwd.hpp index 71cb79b1..d2621404 100644 --- a/src/se/subscription_fwd.hpp +++ b/src/se/subscription_fwd.hpp @@ -48,6 +48,8 @@ namespace lean { PeerConnected, /// Peer disconnected PeerDisconnected, + /// Peers total count updated + PeersTotalCountUpdated, /// Data of a block is requested BlockRequest, /// Data of a block is respond diff --git a/tests/unit/blockchain/state_transition_function_test.cpp b/tests/unit/blockchain/state_transition_function_test.cpp index b8578f38..4e03114c 100644 --- a/tests/unit/blockchain/state_transition_function_test.cpp +++ b/tests/unit/blockchain/state_transition_function_test.cpp @@ -8,13 +8,15 @@ #include +#include "testutil/prepare_loggers.hpp" #include "mock/metrics_mock.hpp" #include "types/config.hpp" #include "types/state.hpp" TEST(STF, Test) { auto metrics = std::make_shared(); - lean::STF stf(metrics); + auto logsys = testutil::prepareLoggers(); + lean::STF stf(metrics, logsys->getLogger("STF", "test")); lean::Config config{ .genesis_time = 0,