diff --git a/scripts/gen_shadow_yaml.sh b/scripts/gen_shadow_yaml.sh index 2b0cb7ef..8ec2977e 100755 --- a/scripts/gen_shadow_yaml.sh +++ b/scripts/gen_shadow_yaml.sh @@ -13,6 +13,10 @@ # -x QLEAN_PATH Path to qlean executable (default: /build/src/executable/qlean) # -m MODULES_DIR Path to modules dir (default: /build/src/modules) # -r PROJECT_ROOT Project root to use for defaults (default: parent dir of this script) +# -G GRAPH_FILE Path to GML graph file (default: atlas_v201801.shadow_v2.gml.xz) +# -b MAX_BOOTNODES Max bootnodes to pass to each node (short for --max-bootnodes) +# --use-inline-graph Use inline graph instead of external GML file (default: use external graph) +# --max-bootnodes Max bootnodes to pass to each node (supported: --max-bootnodes=5 or --max-bootnodes 5) # # Notes: # - Node count is inferred from node_*.key files in GENESIS_DIR. @@ -40,6 +44,9 @@ MODULES_DIR_DEFAULT="$PROJECT_ROOT/build/src/modules" QLEAN_PATH="$QLEAN_PATH_DEFAULT" MODULES_DIR="$MODULES_DIR_DEFAULT" GENESIS_DIR="" +GRAPH_FILE="atlas_v201801.shadow_v2.gml.xz" +USE_INLINE_GRAPH=false +MAX_BOOTNODES="" # Network/graph defaults (host/switched bandwidth, latency, packet loss) BANDWIDTH_HOST="100 Mbit" @@ -47,7 +54,7 @@ BANDWIDTH_SWITCH="1 Gbit" LINK_LATENCY="1 ms" PACKET_LOSS="0.0" -while getopts ":g:o:t:u:p:i:x:m:r:h" opt; do +while getopts ":g:o:t:u:p:i:x:m:r:G:b:h-:" opt; do case $opt in g) GENESIS_DIR="$OPTARG" ;; o) OUTPUT_YAML="$OPTARG" ;; @@ -58,7 +65,20 @@ while getopts ":g:o:t:u:p:i:x:m:r:h" opt; do x) QLEAN_PATH="$OPTARG" ;; m) MODULES_DIR="$OPTARG" ;; r) PROJECT_ROOT="$OPTARG" ; QLEAN_PATH_DEFAULT="$PROJECT_ROOT/build/src/executable/qlean"; MODULES_DIR_DEFAULT="$PROJECT_ROOT/build/src/modules" ;; + G) GRAPH_FILE="$OPTARG" ;; + b) MAX_BOOTNODES="$OPTARG" ;; h) print_usage; exit 0 ;; + -) case "${OPTARG}" in + use-inline-graph) USE_INLINE_GRAPH=true ;; + max-bootnodes) + # support: --max-bootnodes 5 + MAX_BOOTNODES="${!OPTIND}" + OPTIND=$((OPTIND + 1)) ;; + max-bootnodes=*) + # support: --max-bootnodes=5 + MAX_BOOTNODES="${OPTARG#*=}" ;; + *) echo "Error: Invalid option --${OPTARG}" >&2; print_usage; exit 2 ;; + esac ;; :) echo "Error: Option -$OPTARG requires an argument" >&2; print_usage; exit 2 ;; \?) echo "Error: Invalid option -$OPTARG" >&2; print_usage; exit 2 ;; esac @@ -96,9 +116,11 @@ GENESIS_DIR_ABS="$(py_abspath "$GENESIS_DIR")" QLEAN_PATH_ABS="$(py_abspath "$QLEAN_PATH")" MODULES_DIR_ABS="$(py_abspath "$MODULES_DIR")" OUTPUT_YAML_ABS="$(py_abspath "$(dirname "$OUTPUT_YAML")")/$(basename "$OUTPUT_YAML")" +GRAPH_FILE_ABS="$(py_abspath "$GRAPH_FILE")" CONFIG_YAML="$GENESIS_DIR_ABS/config.yaml" VALIDATORS_YAML="$GENESIS_DIR_ABS/validators.yaml" +VALIDATOR_KEYS_MANIFEST_YAML="$GENESIS_DIR_ABS/hash-sig-keys/validator-keys-manifest.yaml" VALIDATOR_CONFIG_YAML="$GENESIS_DIR_ABS/validator-config.yaml" NODES_YAML="$GENESIS_DIR_ABS/nodes.yaml" @@ -209,51 +231,60 @@ mkdir -p "$(dirname "$OUTPUT_YAML_ABS")" printf "experimental:\n" printf " native_preemption_enabled: true\n" printf "network:\n" - # Emit an inline GML graph: create one node per host with 100 Mbit and a central switch with 1 Gbit printf " graph:\n" printf " type: gml\n" - printf " inline: |\n" - printf " graph [\n" - printf " directed 0\n" + + if [[ "$USE_INLINE_GRAPH" == "true" ]]; then + # Emit inline GML graph + printf " inline: |\n" + printf " graph [\n" + printf " directed 0\n" - # Print node entries for each host - for ((i=0; i.") + ("shadow", "Run with shadow compatibility (fake xmss provider)") ; po::options_description storage_options("Storage options"); @@ -405,7 +407,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 +507,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; } @@ -608,55 +612,66 @@ namespace lean::app { return Error::InvalidValue; } - // 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"); - return Error::InvalidValue; - } - if (config_->xmss_secret_key_path_.empty()) { - SL_ERROR(logger_, "The '--xmss-sk' (XMSS secret key) path must be provided"); - return Error::InvalidValue; + if (find_argument(cli_values_map_, "shadow")) { + config_->fake_xmss_ = true; } + if (not config_->fakeXmss()) { + // 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"); + return Error::InvalidValue; + } + if (config_->xmss_secret_key_path_.empty()) { + SL_ERROR(logger_, + "The '--xmss-sk' (XMSS secret key) path must be provided"); + return Error::InvalidValue; + } - config_->xmss_public_key_path_ = - resolve_relative(config_->xmss_public_key_path_, "xmss-pk"); - if (not is_regular_file(config_->xmss_public_key_path_)) { - SL_ERROR(logger_, - "The 'xmss-pk' file does not exist or is not a file: {}", - config_->xmss_public_key_path_); - return Error::InvalidValue; - } + config_->xmss_public_key_path_ = + resolve_relative(config_->xmss_public_key_path_, "xmss-pk"); + if (not is_regular_file(config_->xmss_public_key_path_)) { + SL_ERROR(logger_, + "The 'xmss-pk' file does not exist or is not a file: {}", + config_->xmss_public_key_path_); + return Error::InvalidValue; + } - config_->xmss_secret_key_path_ = - resolve_relative(config_->xmss_secret_key_path_, "xmss-sk"); - if (not is_regular_file(config_->xmss_secret_key_path_)) { - SL_ERROR(logger_, - "The 'xmss-sk' file does not exist or is not a file: {}", - config_->xmss_secret_key_path_); - return Error::InvalidValue; - } + config_->xmss_secret_key_path_ = + resolve_relative(config_->xmss_secret_key_path_, "xmss-sk"); + if (not is_regular_file(config_->xmss_secret_key_path_)) { + SL_ERROR(logger_, + "The 'xmss-sk' file does not exist or is not a file: {}", + config_->xmss_secret_key_path_); + return Error::InvalidValue; + } - // Load XMSS keypair from JSON files - 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_); - SL_INFO(logger_, " Secret key: {}", config_->xmss_secret_key_path_); + // Load XMSS keypair from JSON files + BOOST_OUTCOME_TRY( + config_->xmss_keypair_, + crypto::xmss::loadKeypairFromJson(config_->xmss_secret_key_path_, + config_->xmss_public_key_path_)); + SL_INFO(logger_, "Loaded XMSS keypair from:"); + SL_INFO(logger_, " Public key: {}", config_->xmss_public_key_path_); + SL_INFO(logger_, " Secret key: {}", config_->xmss_secret_key_path_); + } else { + config_->xmss_keypair_ = crypto::xmss::XmssProviderFake::loadKeypair( + config_->xmss_secret_key_path_.string()); + } // 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/commands/generate_genesis.hpp b/src/commands/generate_genesis.hpp index 83924e05..fca13d88 100644 --- a/src/commands/generate_genesis.hpp +++ b/src/commands/generate_genesis.hpp @@ -55,35 +55,41 @@ inline int cmdGenerateGenesis(auto &&getArg) { auto hashsig_directory = genesis_directory / "hash-sig-keys"; std::filesystem::create_directories(hashsig_directory); + auto fake_xmss = shadow; + std::vector xmss_public_keys; - for (size_t index = 0; index < validator_count; ++index) { - auto xmss_public_key_path = - hashsig_directory / xmss_public_key_name(index); - auto xmss_private_key_path = - hashsig_directory / xmss_private_key_name(index); - if (std::filesystem::exists(xmss_public_key_path) - and std::filesystem::exists(xmss_private_key_path)) { - auto keypair_result = lean::crypto::xmss::loadKeypairFromJson( - xmss_private_key_path, xmss_public_key_path); - if (not keypair_result) { - fmt::println(std::cerr, - "Error loading XMSS keypair: {}", - keypair_result.error().message()); - fmt::println(std::cerr, " {}", xmss_public_key_path.string()); - fmt::println(std::cerr, " {}", xmss_private_key_path.string()); - return EXIT_FAILURE; + if (not fake_xmss) { + for (size_t index = 0; index < validator_count; ++index) { + auto xmss_public_key_path = + hashsig_directory / xmss_public_key_name(index); + auto xmss_private_key_path = + hashsig_directory / xmss_private_key_name(index); + if (std::filesystem::exists(xmss_public_key_path) + and std::filesystem::exists(xmss_private_key_path)) { + auto keypair_result = lean::crypto::xmss::loadKeypairFromJson( + xmss_private_key_path, xmss_public_key_path); + if (not keypair_result) { + fmt::println(std::cerr, + "Error loading XMSS keypair: {}", + keypair_result.error().message()); + fmt::println(std::cerr, " {}", xmss_public_key_path.string()); + fmt::println(std::cerr, " {}", xmss_private_key_path.string()); + return EXIT_FAILURE; + } + auto &keypair = keypair_result.value(); + xmss_public_keys.emplace_back(keypair.public_key); + } else { + fmt::println( + std::cerr, "Generating XMSS keypair for validator {}", index); + auto keypair = lean::crypto::xmss::XmssProviderImpl{}.generateKeypair( + xmss_activation_epoch, xmss_active_epoch); + write_json(xmss_private_key_path, keypair.private_key); + write_json(xmss_public_key_path, keypair.public_key); + xmss_public_keys.emplace_back(keypair.public_key); } - auto &keypair = keypair_result.value(); - xmss_public_keys.emplace_back(keypair.public_key); - } else { - fmt::println( - std::cerr, "Generating XMSS keypair for validator {}", index); - auto keypair = lean::crypto::xmss::XmssProviderImpl{}.generateKeypair( - xmss_activation_epoch, xmss_active_epoch); - write_json(xmss_private_key_path, keypair.private_key); - write_json(xmss_public_key_path, keypair.public_key); - xmss_public_keys.emplace_back(keypair.public_key); } + } else { + xmss_public_keys.resize(validator_count); } build_yaml( @@ -110,7 +116,7 @@ inline int cmdGenerateGenesis(auto &&getArg) { build_yaml(genesis_directory / "config.yaml", [&](YAML::Node &yaml) { yaml["GENESIS_TIME"] = genesis_time; yaml["VALIDATOR_COUNT"] = validator_count; - auto &&yaml_validators = yaml["VALIDATORS"]; + auto &&yaml_validators = yaml["GENESIS_VALIDATORS"]; for (auto &xmss_public_key : xmss_public_keys) { yaml_validators.push_back(xmss_public_key.toHex()); } diff --git a/src/crypto/xmss/CMakeLists.txt b/src/crypto/xmss/CMakeLists.txt index 1351e6a5..0e461022 100644 --- a/src/crypto/xmss/CMakeLists.txt +++ b/src/crypto/xmss/CMakeLists.txt @@ -5,6 +5,7 @@ # add_library(xmss_provider + xmss_provider_fake.cpp xmss_provider_impl.cpp xmss_util.cpp ) diff --git a/src/crypto/xmss/xmss_provider_fake.cpp b/src/crypto/xmss/xmss_provider_fake.cpp new file mode 100644 index 00000000..b66dbf49 --- /dev/null +++ b/src/crypto/xmss/xmss_provider_fake.cpp @@ -0,0 +1,52 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "crypto/xmss/xmss_provider_fake.hpp" + +#include + +#include + +#include "utils/tuple_hash.hpp" + +namespace lean::crypto::xmss { + XmssKeypair XmssProviderFake::generateKeypair(uint64_t, uint64_t) { + abort(); + } + + XmssSignature XmssProviderFake::sign(XmssPrivateKey private_key, + uint32_t epoch, + qtils::BytesIn message) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto key_index = reinterpret_cast(private_key.get()); + auto args = std::tuple{key_index, epoch, qtils::BytesStdHash{}(message)}; + XmssSignature signature; + uint32_t seed = std::hash{}(args); + std::independent_bits_engine random( + seed); + std::ranges::generate(signature, random); + return signature; + } + + bool XmssProviderFake::verify(XmssPublicKey, + qtils::BytesIn, + uint32_t, + XmssSignature) { + return true; + } + + XmssKeypair XmssProviderFake::loadKeypair(std::string_view private_key_path) { + size_t key_index = std::hash{}(private_key_path); + return XmssKeypair{ + .private_key = + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(key_index), + [](PQSecretKey *) {}, + }, + }; + } +} // namespace lean::crypto::xmss diff --git a/src/crypto/xmss/xmss_provider_fake.hpp b/src/crypto/xmss/xmss_provider_fake.hpp new file mode 100644 index 00000000..13c3bd68 --- /dev/null +++ b/src/crypto/xmss/xmss_provider_fake.hpp @@ -0,0 +1,33 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "crypto/xmss/xmss_provider.hpp" + +namespace lean::crypto::xmss { + /** + * Fake xmss provider implementation. + * Used for shadow simulations. + * `sign` returns pseudo-random `signature` seeded by `epoch` and `message`, + * to prevent snappy from efficiently compressing zeros. + * `verify` always return true. + */ + class XmssProviderFake : public XmssProvider { + public: + // XmssProvider + XmssKeypair generateKeypair(uint64_t, uint64_t) override; + XmssSignature sign(XmssPrivateKey, + uint32_t epoch, + qtils::BytesIn message) override; + bool verify(XmssPublicKey, + qtils::BytesIn, + uint32_t, + XmssSignature) override; + + static XmssKeypair loadKeypair(std::string_view private_key_path); + }; +} // namespace lean::crypto::xmss diff --git a/src/injector/node_injector.cpp b/src/injector/node_injector.cpp index 17ae7e06..7e206cfc 100644 --- a/src/injector/node_injector.cpp +++ b/src/injector/node_injector.cpp @@ -35,6 +35,7 @@ #include "blockchain/impl/validator_registry_impl.hpp" #include "clock/impl/clock_impl.hpp" #include "crypto/hasher/hasher_impl.hpp" +#include "crypto/xmss/xmss_provider_fake.hpp" #include "crypto/xmss/xmss_provider_impl.hpp" #include "injector/bind_by_lambda.hpp" #include "loaders/loader.hpp" @@ -74,6 +75,12 @@ namespace { auto makeApplicationInjector(std::shared_ptr logsys, std::shared_ptr app_config, Ts &&...args) { + std::shared_ptr xmss_provider; + if (not app_config->fakeXmss()) { + xmss_provider = std::make_shared(); + } else { + xmss_provider = std::make_shared(); + } // clang-format off return di::make_injector( di::bind.to(app_config), @@ -105,7 +112,7 @@ namespace { di::bind.to(), di::bind.to(), di::bind.to(), - di::bind.to(), + di::bind.to(xmss_provider), // user-defined overrides... std::forward(args)...); diff --git a/src/modules/networking/networking.cpp b/src/modules/networking/networking.cpp index 68816a10..8092c334 100644 --- a/src/modules/networking/networking.cpp +++ b/src/modules/networking/networking.cpp @@ -37,6 +37,7 @@ #include "modules/networking/ssz_snappy.hpp" #include "modules/networking/status_protocol.hpp" #include "modules/networking/types.hpp" +#include "utils/debug.hpp" namespace lean::modules { constexpr std::chrono::seconds kConnectToPeersTimer{5}; @@ -392,6 +393,9 @@ namespace lean::modules { if (not self) { return; } + SL_INFO(self->logger_, + "receive-attestation-{}", + Debug{signed_attestation.message}); auto res = self->fork_choice_store_->onAttestation(signed_attestation, false); if (not res.has_value()) { @@ -418,6 +422,7 @@ namespace lean::modules { void NetworkingImpl::onSendSignedBlock( std::shared_ptr message) { + SL_INFO(logger_, "publish-block-{}", Debug{message->notification.message}); boost::asio::post(*io_context_, [self{shared_from_this()}, message] { self->gossip_blocks_topic_->publish( encodeSszSnappy(message->notification)); @@ -426,6 +431,9 @@ namespace lean::modules { void NetworkingImpl::onSendSignedVote( std::shared_ptr message) { + SL_INFO(logger_, + "publish-attestation-{}", + Debug{message->notification.message}); boost::asio::post(*io_context_, [self{shared_from_this()}, message] { self->gossip_votes_topic_->publish( encodeSszSnappy(message->notification)); @@ -496,6 +504,9 @@ namespace lean::modules { void NetworkingImpl::receiveBlock( std::optional from_peer, SignedBlockWithAttestation &&signed_block_with_attestation) { + SL_INFO(logger_, + "receive-block-{}", + Debug{signed_block_with_attestation.message}); auto slot_hash = signed_block_with_attestation.message.block.slotHash(); SL_INFO(logger_, "receiveBlock slot {} hash {} parent {}", diff --git a/src/utils/debug.hpp b/src/utils/debug.hpp new file mode 100644 index 00000000..4d8cd064 --- /dev/null +++ b/src/utils/debug.hpp @@ -0,0 +1,57 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "types/block_with_attestation.hpp" + +namespace lean { + template + struct Debug { + const T &v; + }; +} // namespace lean + +template <> +struct fmt::formatter> { + constexpr auto parse(const auto &ctx) { + return ctx.end(); + } + auto format(const lean::Debug &v, + fmt::format_context &ctx) const { + return fmt::format_to(ctx.out(), + "[{}-{}-{}-{}-{}]", + v.v.validator_id, + v.v.data.slot, + v.v.data.head.slot, + v.v.data.target.slot, + v.v.data.source.slot); + } +}; + +template <> +struct fmt::formatter> { + constexpr auto parse(const auto &ctx) { + return ctx.end(); + } + auto format(const lean::Debug &v, + fmt::format_context &ctx) const { + return fmt::format_to( + ctx.out(), + "[{}-{}-{}-[{}]-{}]", + v.v.block.slot, + v.v.block.hash(), + v.v.block.parent_root, + fmt::join(v.v.block.body.attestations.data() + | std::views::transform([](const lean::Attestation &v) { + return lean::Debug{v}; + }), + "-"), + lean::Debug{v.v.proposer_attestation}); + } +};