From 15d1dd804d5f8a6024dd85205657d944190c200a Mon Sep 17 00:00:00 2001 From: turuslan Date: Tue, 19 May 2026 15:55:30 +0500 Subject: [PATCH 01/12] /lean/v0/test_driver/verify_signatures/run Signed-off-by: turuslan --- src/app/configurator.cpp | 13 +++- src/app/configurator.hpp | 2 + src/app/impl/http_server.cpp | 43 ++++------- src/commands/test_driver.hpp | 136 ++++++++++++++++++++++++++++++++++ src/executable/CMakeLists.txt | 4 + src/executable/lean_node.cpp | 12 +-- src/serde/json.hpp | 9 +++ src/utils/http.cpp | 8 ++ src/utils/http.hpp | 2 + 9 files changed, 190 insertions(+), 39 deletions(-) create mode 100644 src/commands/test_driver.hpp diff --git a/src/app/configurator.cpp b/src/app/configurator.cpp index fef51eb1..785d47e3 100644 --- a/src/app/configurator.cpp +++ b/src/app/configurator.cpp @@ -121,7 +121,6 @@ namespace lean::app { config_->database_.directory = "db"; config_->database_.cache_size = 512 << 20; // 512MiB - config_->metrics_.endpoint = {boost::asio::ip::address_v4::any(), 9615}; config_->metrics_.enabled = std::nullopt; namespace po = boost::program_options; @@ -196,6 +195,8 @@ namespace lean::app { ("version,v", "show version") ("config,c", po::value(), "config-file path") ("log,l", po::value>(), "Sets a custom logging filter") + ("api-host", po::value(), "Set address for OpenMetrics over HTTP.") + ("api-port", po::value(), "Set port for OpenMetrics over HTTP.") ; // clang-format on @@ -250,6 +251,9 @@ namespace lean::app { logger_cli_args_ = vm["log"].as>(); } + BOOST_OUTCOME_TRY( + parseEndpoint(config_->api_endpoint_, vm, "api-host", "api-port")); + return false; } @@ -294,6 +298,10 @@ namespace lean::app { return load_default(); } + const boost::asio::ip::tcp::endpoint &Configurator::apiEndpoint() const { + return config_->apiEndpoint(); + } + outcome::result> Configurator::calculateConfig( qtils::SharedRef logger) { logger_ = std::move(logger); @@ -779,9 +787,6 @@ namespace lean::app { config_->metrics_.enabled = false; } - BOOST_OUTCOME_TRY(parseEndpoint( - config_->api_endpoint_, cli_values_map_, "api-host", "api-port")); - if (not config_->node_key_.has_value()) { config_->node_key_ = randomKeyPair(); SL_INFO(logger_, "Generating random node key"); diff --git a/src/app/configurator.hpp b/src/app/configurator.hpp index 4d58ba50..590ccf4c 100644 --- a/src/app/configurator.hpp +++ b/src/app/configurator.hpp @@ -5,6 +5,7 @@ #pragma once +#include #include #include #include @@ -53,6 +54,7 @@ namespace lean::app { std::vector getLoggingCliArgs() { return logger_cli_args_; } + const boost::asio::ip::tcp::endpoint &apiEndpoint() const; outcome::result> calculateConfig( qtils::SharedRef logger); diff --git a/src/app/impl/http_server.cpp b/src/app/impl/http_server.cpp index d8d37d93..a2f4e96c 100644 --- a/src/app/impl/http_server.cpp +++ b/src/app/impl/http_server.cpp @@ -23,8 +23,6 @@ #include "utils/http.hpp" namespace lean::app { - constexpr auto *kContentTypeJson = "application/json; charset=utf-8"; - struct Enabled { bool enabled; @@ -93,11 +91,8 @@ namespace lean::app { std::string_view{request.method_string()}, url); if (url == "/lean/v0/health") { - response.set(boost::beast::http::field::content_type, - kContentTypeJson); - response.body() = - R"({"status":"healthy","service":"lean-rpc-api"})"; - return response; + return http::respondJson( + R"({"status":"healthy","service":"lean-rpc-api"})"); } if (url == "/lean/v0/states/finalized") { auto finalized = self->fork_choice_store_->getLatestFinalized(); @@ -115,34 +110,27 @@ namespace lean::app { } if (url == "/lean/v0/checkpoints/justified") { auto justified = self->fork_choice_store_->getLatestJustified(); - response.set(boost::beast::http::field::content_type, - kContentTypeJson); - response.body() = std::format(R"({{"root":"0x{}","slot":{}}})", - justified.root.toHex(), - justified.slot); - return response; + return http::respondJson( + std::format(R"({{"root":"0x{}","slot":{}}})", + justified.root.toHex(), + justified.slot)); } if (url == "/lean/v0/fork_choice") { if (auto result_res = self->fork_choice_store_->apiForkChoice()) { auto &result = result_res.value(); - response.set(boost::beast::http::field::content_type, - kContentTypeJson); - response.body() = json::encode(json::NameCase::SNAKE, result); - } else { - response.result( - boost::beast::http::status::internal_server_error); + return http::respondJson( + json::encode(json::NameCase::SNAKE, result)); } + response.result( + boost::beast::http::status::internal_server_error); return response; } if (url == "/lean/v0/admin/aggregator") { if (request.method() == boost::beast::http::verb::get) { auto is_aggregator = self->chain_spec_->isAggregator(); - response.set(boost::beast::http::field::content_type, - kContentTypeJson); - response.body() = - std::format(R"({{"is_aggregator":{}}})", is_aggregator); - return response; + return http::respondJson( + std::format(R"({{"is_aggregator":{}}})", is_aggregator)); } if (request.method() == boost::beast::http::verb::post) { Enabled body; @@ -155,13 +143,10 @@ namespace lean::app { } auto enabled = body.enabled; auto previous = self->chain_spec_->setIsAggregator(enabled); - response.set(boost::beast::http::field::content_type, - kContentTypeJson); - response.body() = + return http::respondJson( std::format(R"({{"is_aggregator":{},"previous":{}}})", enabled, - previous); - return response; + previous)); } } response.result(boost::beast::http::status::not_found); diff --git a/src/commands/test_driver.hpp b/src/commands/test_driver.hpp new file mode 100644 index 00000000..e65502be --- /dev/null +++ b/src/commands/test_driver.hpp @@ -0,0 +1,136 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +#include "blockchain/fork_choice.hpp" +#include "crypto/xmss/xmss_provider_impl.hpp" +#include "log/logger.hpp" +#include "mock/app/validator_keys_manifest_mock.hpp" +#include "mock/blockchain/block_storage_mock.hpp" +#include "mock/blockchain/block_tree_mock.hpp" +#include "mock/blockchain/validator_registry_mock.hpp" +#include "mock/metrics_mock.hpp" +#include "serde/json.hpp" +#include "types/signed_block.hpp" +#include "types/state.hpp" +#include "utils/http.hpp" + +struct VerifySignaturesRequest { + lean::State anchor_state; + lean::SignedBlock signed_block; + + JSON_FIELDS(anchor_state, signed_block); +}; + +struct VerifySignaturesResponse { + bool succeeded; + std::optional error; + + JSON_FIELDS(succeeded, error); +}; + +template +lean::http::Response httpJson(const lean::http::Request &request, auto &&f) { + lean::http::Response response; + RequestJson request_json; + try { + lean::json::decode( + lean::json::NameCase::CAMEL, request_json, request.body()); + } catch (std::exception &e) { + response.result(boost::beast::http::status::bad_request); + response.body() = e.what(); + return response; + } + auto response_json = f(request_json); + return lean::http::respondJson( + lean::json::encode(lean::json::NameCase::CAMEL, response_json)); +} + +inline int cmdTestDriver(std::shared_ptr logsys, + boost::asio::ip::tcp::endpoint api_endpoint) { + auto log = logsys->getLogger("TestDriver", "http"); + lean::http::ServerConfig config_api{ + .endpoint = api_endpoint, + .on_request = + [logsys, log](lean::http::Request request) { + lean::http::Response response; + std::string_view url{request.target()}; + SL_INFO( + log, "{} {}", std::string_view{request.method_string()}, url); + if (url == "/lean/v0/health") { + return lean::http::respondJson( + R"({"status":"healthy","service":"lean-rpc-api"})"); + } + if (url == "/lean/v0/test_driver/verify_signatures/run") { + return httpJson( + request, [&](VerifySignaturesRequest request) { + lean::ValidatorRegistry::ValidatorIndices validator_indices{ + 0}; + auto validator_registry = + std::make_shared(); + EXPECT_CALL(*validator_registry, currentValidatorIndices()) + .Times(testing::AnyNumber()) + .WillRepeatedly(testing::ReturnRef(validator_indices)); + auto block_storage = + std::make_shared(); + EXPECT_CALL( + *block_storage, + getState(request.signed_block.block.parent_root)) + .WillOnce(testing::Return(request.anchor_state)); + lean::ForkChoiceStore store{ + {}, + logsys, + std::make_shared(), + {}, + {}, + {}, + {}, + {}, + 0, + validator_registry, + std::make_shared< + lean::app::ValidatorKeysManifestMock>(), + std::make_shared< + lean::crypto::xmss::XmssProviderImpl>(), + std::make_shared(), + block_storage, + false, + 1, + }; + return VerifySignaturesResponse{ + .succeeded = + store.validateBlockSignatures(request.signed_block), + }; + }); + } + request.body().size(); + response.result(boost::beast::http::status::not_found); + return response; + }, + }; + boost::asio::io_context io_context; + if (auto res = lean::http::serve(log, io_context, config_api); + not res.has_value()) { + SL_WARN(log, + "listen api {}:{} error: {}", + api_endpoint.address().to_string(), + api_endpoint.port(), + res.error()); + return EXIT_FAILURE; + } + SL_INFO(log, + "listen api {}:{}", + api_endpoint.address().to_string(), + api_endpoint.port()); + io_context.run(); + return EXIT_SUCCESS; +} diff --git a/src/executable/CMakeLists.txt b/src/executable/CMakeLists.txt index dc24b629..841a4673 100644 --- a/src/executable/CMakeLists.txt +++ b/src/executable/CMakeLists.txt @@ -48,6 +48,10 @@ endif () set_target_properties(lean_node PROPERTIES OUTPUT_NAME qlean) add_dependencies(lean_node all_modules) +find_package(GTest CONFIG REQUIRED) +target_include_directories(lean_node PRIVATE ${PROJECT_SOURCE_DIR}/tests) +target_link_libraries(lean_node GTest::gmock) + #if (BACKWARD) # add_backward(lean_node) #endif () diff --git a/src/executable/lean_node.cpp b/src/executable/lean_node.cpp index 0b08b92e..18f9a5ad 100644 --- a/src/executable/lean_node.cpp +++ b/src/executable/lean_node.cpp @@ -20,6 +20,7 @@ #include "app/configurator.hpp" #include "commands/generate_genesis.hpp" #include "commands/key_generate_node_key.hpp" +#include "commands/test_driver.hpp" #include "injector/node_injector.hpp" #include "loaders/loader.hpp" #include "log/logger.hpp" @@ -93,12 +94,6 @@ int main(int argc, const char **argv, const char **env) { return EXIT_FAILURE; } - if (argc == 1) { - // Run without arguments - wrong_usage(); - return EXIT_FAILURE; - } - if (getArg(1) == "key" and getArg(2) == "generate-node-key") { cmdKeyGenerateNodeKey(); return EXIT_SUCCESS; @@ -157,6 +152,11 @@ int main(int argc, const char **argv, const char **env) { } } + if (auto *s = getenv("HIVE_LEAN_TEST_DRIVER"); + s != nullptr and std::string_view{s} == "1") { + return cmdTestDriver(logging_system, app_configurator->apiEndpoint()); + } + // Parse remaining args if (auto res = app_configurator->step2(); res.has_value()) { if (res.value()) { diff --git a/src/serde/json.hpp b/src/serde/json.hpp index ca0b16b0..810a201e 100644 --- a/src/serde/json.hpp +++ b/src/serde/json.hpp @@ -89,6 +89,15 @@ namespace lean::json { } } + template + void encode(JsonOut json, const std::optional &v) { + if (v.has_value()) { + encode(json, *v); + } else { + json.v.SetNull(); + } + } + template void encode(JsonOut json, const qtils::ByteArr &v) { encode(json, fmt::format("{:0xx}", qtils::Hex{v})); diff --git a/src/utils/http.cpp b/src/utils/http.cpp index 24a339ab..092a0e6f 100644 --- a/src/utils/http.cpp +++ b/src/utils/http.cpp @@ -84,4 +84,12 @@ namespace lean::http { }); return outcome::success(); } + + Response respondJson(std::string_view json) { + Response response; + response.set(boost::beast::http::field::content_type, + "application/json; charset=utf-8"); + response.body() = json; + return response; + } } // namespace lean::http diff --git a/src/utils/http.hpp b/src/utils/http.hpp index dd77d84d..0c8dddee 100644 --- a/src/utils/http.hpp +++ b/src/utils/http.hpp @@ -39,4 +39,6 @@ namespace lean::http { outcome::result serve(log::Logger log, boost::asio::io_context &io_context, ServerConfig config); + + Response respondJson(std::string_view json); } // namespace lean::http From 4f8c4b2ff8cbfb8089d247d8aa714a7374b48515 Mon Sep 17 00:00:00 2001 From: turuslan Date: Tue, 19 May 2026 17:07:34 +0500 Subject: [PATCH 02/12] check proposer index Signed-off-by: turuslan --- src/blockchain/fork_choice.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/blockchain/fork_choice.cpp b/src/blockchain/fork_choice.cpp index 528920ff..50f652eb 100644 --- a/src/blockchain/fork_choice.cpp +++ b/src/blockchain/fork_choice.cpp @@ -831,6 +831,10 @@ namespace lean { auto &parent_state = *parent_state_res.value(); const auto &validators = parent_state.validators; + if (block.proposer_index >= validators.size()) { + return false; + } + for (auto &&[aggregated_attestation, aggregated_signature] : std::views::zip(aggregated_attestations, attestation_signatures)) { if (not validateAggregatedSignature(parent_state, From 8711f2471c94939b50593d9a3b5dd1754bcad55d Mon Sep 17 00:00:00 2001 From: turuslan Date: Tue, 19 May 2026 17:07:44 +0500 Subject: [PATCH 03/12] remove unused Signed-off-by: turuslan --- src/commands/test_driver.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/test_driver.hpp b/src/commands/test_driver.hpp index e65502be..c044c203 100644 --- a/src/commands/test_driver.hpp +++ b/src/commands/test_driver.hpp @@ -112,7 +112,6 @@ inline int cmdTestDriver(std::shared_ptr logsys, }; }); } - request.body().size(); response.result(boost::beast::http::status::not_found); return response; }, From 9cc49444196e2af95a0d72743799da6056b79c16 Mon Sep 17 00:00:00 2001 From: turuslan Date: Tue, 19 May 2026 17:28:12 +0500 Subject: [PATCH 04/12] /lean/v0/test_driver/state_transition/run Signed-off-by: turuslan --- src/commands/test_driver.hpp | 72 ++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/commands/test_driver.hpp b/src/commands/test_driver.hpp index c044c203..e805fd4c 100644 --- a/src/commands/test_driver.hpp +++ b/src/commands/test_driver.hpp @@ -38,6 +38,33 @@ struct VerifySignaturesResponse { JSON_FIELDS(succeeded, error); }; +struct StateTransitionRequest { + lean::State pre; + std::vector blocks; + + JSON_FIELDS(pre, blocks); +}; + +struct StateTransitionPost { + lean::Slot slot; + lean::Slot latest_block_header_slot; + lean::BlockHash latest_block_header_state_root; + size_t historical_block_hashes_count; + + JSON_FIELDS(slot, + latest_block_header_slot, + latest_block_header_state_root, + historical_block_hashes_count); +}; + +struct StateTransitionResponse { + bool succeeded; + std::optional error; + std::optional post; + + JSON_FIELDS(succeeded, error, post); +}; + template lean::http::Response httpJson(const lean::http::Request &request, auto &&f) { lean::http::Response response; @@ -112,6 +139,51 @@ inline int cmdTestDriver(std::shared_ptr logsys, }; }); } + if (url == "/lean/v0/test_driver/state_transition/run") { + return httpJson( + request, [&](StateTransitionRequest request) { + auto block_tree = + std::make_shared(); + EXPECT_CALL(*block_tree, getLatestJustified()) + .Times(testing::AnyNumber()); + EXPECT_CALL(*block_tree, lastFinalized()) + .Times(testing::AnyNumber()); + lean::STF stf{ + logsys, + block_tree, + std::make_shared(), + }; + auto stf_many = [&]() -> outcome::result { + auto state = request.pre; + for (auto &block : request.blocks) { + BOOST_OUTCOME_TRY( + state, stf.stateTransition(block, state, true)); + } + return state; + }; + auto state_result = stf_many(); + if (not state_result.has_value()) { + return StateTransitionResponse{ + .succeeded = false, + .error = state_result.error().message(), + }; + } + auto &state = state_result.value(); + return StateTransitionResponse{ + .succeeded = true, + .post = + StateTransitionPost{ + .slot = state.slot, + .latest_block_header_slot = + state.latest_block_header.slot, + .latest_block_header_state_root = + state.latest_block_header.hash(), + .historical_block_hashes_count = + state.historical_block_hashes.size(), + }, + }; + }); + } response.result(boost::beast::http::status::not_found); return response; }, From 9cce11c9b2d0cf2c085a8ebc08196b9ab7fa1129 Mon Sep 17 00:00:00 2001 From: turuslan Date: Tue, 19 May 2026 19:36:34 +0500 Subject: [PATCH 05/12] /lean/v0/test_driver/fork_choice/step Signed-off-by: turuslan --- src/app/configuration.hpp | 3 +- src/commands/test_driver.hpp | 302 +++++++++++++++++- .../types}/fork_choice_test_json.hpp | 0 tests/test_vectors/fork_choice_test.cpp | 2 +- 4 files changed, 300 insertions(+), 7 deletions(-) rename {tests/test_vectors => src/types}/fork_choice_test_json.hpp (100%) diff --git a/src/app/configuration.hpp b/src/app/configuration.hpp index 922b7979..26bf1e9c 100644 --- a/src/app/configuration.hpp +++ b/src/app/configuration.hpp @@ -13,13 +13,12 @@ #include #include #include -#include #include "app/validator_keys_manifest.hpp" #include "crypto/xmss/xmss_provider.hpp" namespace lean::app { - class Configuration : Singleton { + class Configuration { public: using Endpoint = boost::asio::ip::tcp::endpoint; diff --git a/src/commands/test_driver.hpp b/src/commands/test_driver.hpp index e805fd4c..9d6fa213 100644 --- a/src/commands/test_driver.hpp +++ b/src/commands/test_driver.hpp @@ -12,14 +12,21 @@ #include #include "blockchain/fork_choice.hpp" +#include "blockchain/impl/anchor_block_impl.hpp" +#include "blockchain/impl/anchor_state_impl.hpp" #include "crypto/xmss/xmss_provider_impl.hpp" #include "log/logger.hpp" +#include "mock/app/chain_spec_mock.hpp" +#include "mock/app/configuration_mock.hpp" #include "mock/app/validator_keys_manifest_mock.hpp" #include "mock/blockchain/block_storage_mock.hpp" #include "mock/blockchain/block_tree_mock.hpp" #include "mock/blockchain/validator_registry_mock.hpp" +#include "mock/clock/manual_clock.hpp" +#include "mock/crypto/xmss_provider_mock.hpp" #include "mock/metrics_mock.hpp" #include "serde/json.hpp" +#include "types/fork_choice_test_json.hpp" #include "types/signed_block.hpp" #include "types/state.hpp" #include "utils/http.hpp" @@ -65,6 +72,37 @@ struct StateTransitionResponse { JSON_FIELDS(succeeded, error, post); }; +struct ForkChoiceInit { + lean::State anchor_state; + lean::Block anchor_block; + + JSON_FIELDS(anchor_state, anchor_block); +}; + +struct DriverSnapshot { + lean::Slot head_slot; + lean::BlockHash head_root; + uint64_t time; + lean::Checkpoint justified_checkpoint; + lean::Checkpoint finalized_checkpoint; + lean::BlockHash safe_target; + + JSON_FIELDS(head_slot, + head_root, + time, + justified_checkpoint, + finalized_checkpoint, + safe_target); +}; + +struct StepResponse { + bool accepted; + std::optional error; + DriverSnapshot snapshot; + + JSON_FIELDS(accepted, error, snapshot); +}; + template lean::http::Response httpJson(const lean::http::Request &request, auto &&f) { lean::http::Response response; @@ -77,18 +115,148 @@ lean::http::Response httpJson(const lean::http::Request &request, auto &&f) { response.body() = e.what(); return response; } - auto response_json = f(request_json); - return lean::http::respondJson( - lean::json::encode(lean::json::NameCase::CAMEL, response_json)); + auto call = [&] { return f(request_json); }; + if constexpr (std::is_void_v) { + call(); + response.result(boost::beast::http::status::no_content); + return response; + } else { + auto response_json = call(); + return lean::http::respondJson( + lean::json::encode(lean::json::NameCase::CAMEL, response_json)); + } } +struct ForkChoiceDriver { + using BlockHash = lean::BlockHash; + + ForkChoiceDriver(std::shared_ptr logsys, + lean::State init_state) { + using testing::_; + + anchor_state_ = + std::make_shared(init_state); + anchor_block_ = + std::make_shared(*anchor_state_); + + auto app_config = std::make_shared(); + EXPECT_CALL(*app_config, cliSubnetCount()) + .Times(testing::AnyNumber()) + .WillRepeatedly(testing::Return(1)); + + auto validator_registry = std::make_shared(); + EXPECT_CALL(*validator_registry, currentValidatorIndices()) + .Times(testing::AnyNumber()) + .WillRepeatedly(testing::ReturnRef(validator_indices_)); + EXPECT_CALL(*validator_registry, nodeIdByIndex(_)) + .WillRepeatedly( + [](lean::ValidatorIndex i) { return std::format("node_{}", i); }); + + auto chain_spec = std::make_shared(); + EXPECT_CALL(*chain_spec, isAggregator()) + .Times(testing::AnyNumber()) + .WillRepeatedly(testing::Return(true)); + + auto validator_key_manifest = + std::make_shared(); + EXPECT_CALL(*validator_key_manifest, getAllXmssPubkeys()) + .Times(testing::AnyNumber()); + EXPECT_CALL(*validator_key_manifest, getKeypair(_)) + .Times(testing::AnyNumber()) + .WillRepeatedly(testing::Return(std::nullopt)); + + auto xmss = std::make_shared(); + EXPECT_CALL(*xmss, verify(_, _, _, _)) + .WillRepeatedly(testing::Return(true)); + EXPECT_CALL(*xmss, sign(_, _, _)).Times(testing::AnyNumber()); + EXPECT_CALL(*xmss, verifyAggregatedSignatures(_, _, _, _)) + .WillRepeatedly(testing::Return(true)); + EXPECT_CALL(*xmss, aggregateSignatures(_, _, _, _, _, _)) + .Times(testing::AnyNumber()); + + auto block_tree = std::make_shared(); + last_finalized_ = anchor_block_->index(); + last_justified_ = last_finalized_; + blocks_.emplace(anchor_block_->hash(), *anchor_block_); + EXPECT_CALL(*block_tree, lastFinalized()).WillRepeatedly([&] { + return last_finalized_; + }); + EXPECT_CALL(*block_tree, finalize(_)).WillRepeatedly([&](BlockHash hash) { + last_finalized_ = blocks_.at(hash).index(); + return outcome::success(); + }); + EXPECT_CALL(*block_tree, getLatestJustified()).WillRepeatedly([&] { + return last_justified_; + }); + EXPECT_CALL(*block_tree, setJustified(_)) + .WillRepeatedly([&](BlockHash hash) { + last_justified_ = blocks_.at(hash).index(); + return outcome::success(); + }); + EXPECT_CALL(*block_tree, has(_)).WillRepeatedly([&](BlockHash hash) { + return blocks_.contains(hash); + }); + EXPECT_CALL(*block_tree, getSlotByHash(_)) + .WillRepeatedly([&](BlockHash hash) { return blocks_.at(hash).slot; }); + EXPECT_CALL(*block_tree, getBlockHeader(_)) + .WillRepeatedly([&](BlockHash hash) { return blocks_.at(hash); }); + EXPECT_CALL(*block_tree, tryGetBlockHeader(_)) + .WillRepeatedly([&](BlockHash hash) { return blocks_.at(hash); }); + EXPECT_CALL(*block_tree, getChildren(_)) + .WillRepeatedly([&](BlockHash hash) { return children_[hash]; }); + EXPECT_CALL(*block_tree, addBlock(_)) + .WillRepeatedly([&](lean::SignedBlock block) { + auto header = block.block.getHeader(); + header.updateHash(); + blocks_.emplace(header.hash(), header); + children_[header.parent_root].emplace_back(header.hash()); + return outcome::success(); + }); + + auto block_storage = std::make_shared(); + states_.emplace(anchor_block_->hash(), *anchor_state_); + EXPECT_CALL(*block_storage, getState(_)) + .WillRepeatedly([&](BlockHash hash) { return states_.at(hash); }); + EXPECT_CALL(*block_storage, putState(_, _)) + .WillRepeatedly([&](BlockHash hash, lean::State state) { + states_.emplace(hash, state); + return outcome::success(); + }); + + store_.emplace(anchor_state_, + anchor_block_, + std::make_shared(), + logsys, + std::make_shared(), + app_config, + validator_registry, + chain_spec, + validator_key_manifest, + xmss, + block_tree, + block_storage); + store_->dontPropose(); + } + + lean::ValidatorRegistry::ValidatorIndices validator_indices_{0}; + lean::BlockIndex last_finalized_; + lean::BlockIndex last_justified_; + std::shared_ptr anchor_state_; + std::shared_ptr anchor_block_; + std::unordered_map blocks_; + std::unordered_map> children_; + std::unordered_map states_; + std::optional store_; +}; + inline int cmdTestDriver(std::shared_ptr logsys, boost::asio::ip::tcp::endpoint api_endpoint) { auto log = logsys->getLogger("TestDriver", "http"); + auto fork_choice = std::make_shared>(); lean::http::ServerConfig config_api{ .endpoint = api_endpoint, .on_request = - [logsys, log](lean::http::Request request) { + [logsys, log, fork_choice](lean::http::Request request) { lean::http::Response response; std::string_view url{request.target()}; SL_INFO( @@ -184,9 +352,135 @@ inline int cmdTestDriver(std::shared_ptr logsys, }; }); } + if (url == "/lean/v0/test_driver/fork_choice/init") { + return httpJson( + request, [&](ForkChoiceInit request) { + fork_choice->emplace(logsys, request.anchor_state); + }); + } + if (url == "/lean/v0/test_driver/fork_choice/step") { + if (not fork_choice->has_value()) { + response.result(boost::beast::http::status::bad_request); + response.body() = + "\"/lean/v0/test_driver/fork_choice/init\" was not called"; + return response; + } + auto &store = fork_choice->value().store_.value(); + return httpJson( + request, [&](lean::ForkChoiceStep request) { + std::optional> result; + if (auto *tick_step = + std::get_if(&request.v)) { + std::chrono::milliseconds time; + if (tick_step->interval.has_value()) { + time = + (**fork_choice) + .anchor_state_->config.genesisTimeMs() + + *tick_step->interval * lean::INTERVAL_DURATION_MS; + } else if (tick_step->time.has_value()) { + time = std::chrono::seconds{*tick_step->time}; + } else { + throw std::runtime_error{ + "TickStep no interval or time"}; + } + result = [&]() -> outcome::result { + store.onTick(time); + return outcome::success(); + }(); + } else if (auto *block_step = + std::get_if(&request.v)) { + result = [&] { + auto &block = block_step->block; + lean::SignedBlock signed_block{ + .block = block, + .signature = {}, + }; + for (auto &attestation : block.body.attestations) { + signed_block.signature.attestation_signatures + .push_back({.participants = + attestation.aggregation_bits}); + } + auto block_time = + std::chrono::seconds{store.getConfig().genesis_time} + + block.slot * lean::SLOT_DURATION_MS; + store.onTick(block_time); + return store.onBlock(signed_block); + }(); + if (auto &checks = block_step->checks) { + if (checks->block_attestation_count) { + EXPECT_EQ(block_step->block.body.attestations.size(), + *checks->block_attestation_count); + } + if (checks->block_attestations) { + auto &attestations = + block_step->block.body.attestations.data(); + for (auto &check : *checks->block_attestations) { + lean::AggregationBits participants; + for (auto &validator : check.participants) { + participants.add(validator); + } + auto attestation_it = std::ranges::find_if( + attestations, + [&](const lean::AggregatedAttestation + &attestation) { + return attestation.aggregation_bits + == participants; + }); + EXPECT_NE(attestation_it, attestations.end()); + auto &attestation = *attestation_it; + if (check.attestation_slot) { + EXPECT_EQ(attestation.data.slot, + *check.attestation_slot); + } + if (check.target_slot) { + EXPECT_EQ(attestation.data.target.slot, + *check.target_slot); + } + } + } + } + } else if (auto *attestation_step = + std::get_if( + &request.v)) { + result = [&] { + return store.onGossipAttestation( + attestation_step->attestation); + }(); + } else if (auto *aggregated_step = std::get_if< + lean::GossipAggregatedAttestationStep>( + &request.v)) { + result = [&] { + return store.onGossipAggregatedAttestation( + aggregated_step->attestation); + }(); + } + if (not result.value().has_value()) { + return StepResponse{ + .accepted = false, + .error = result->error().message(), + }; + } + auto head = store.getHead(); + return StepResponse{ + .accepted = true, + .snapshot = + DriverSnapshot{ + .head_slot = head.slot, + .head_root = head.root, + .time = store.time().interval, + .justified_checkpoint = + store.getLatestJustified(), + .finalized_checkpoint = + store.getLatestFinalized(), + .safe_target = store.getSafeTarget().root, + }, + }; + }); + } response.result(boost::beast::http::status::not_found); return response; }, + .max_request_size = 1 << 20, }; boost::asio::io_context io_context; if (auto res = lean::http::serve(log, io_context, config_api); diff --git a/tests/test_vectors/fork_choice_test_json.hpp b/src/types/fork_choice_test_json.hpp similarity index 100% rename from tests/test_vectors/fork_choice_test_json.hpp rename to src/types/fork_choice_test_json.hpp diff --git a/tests/test_vectors/fork_choice_test.cpp b/tests/test_vectors/fork_choice_test.cpp index c22f384e..d51f56c5 100644 --- a/tests/test_vectors/fork_choice_test.cpp +++ b/tests/test_vectors/fork_choice_test.cpp @@ -8,7 +8,6 @@ #include "blockchain/impl/anchor_block_impl.hpp" #include "blockchain/impl/anchor_state_impl.hpp" -#include "fork_choice_test_json.hpp" #include "mock/app/chain_spec_mock.hpp" #include "mock/app/configuration_mock.hpp" #include "mock/app/validator_keys_manifest_mock.hpp" @@ -20,6 +19,7 @@ #include "mock/metrics_mock.hpp" #include "test_vectors.hpp" #include "testutil/prepare_loggers.hpp" +#include "types/fork_choice_test_json.hpp" using lean::BlockHash; using testing::_; From 4132df45e73086388c71604e607b7b12f3698d9d Mon Sep 17 00:00:00 2001 From: turuslan Date: Tue, 19 May 2026 23:19:39 +0500 Subject: [PATCH 06/12] fix mock Signed-off-by: turuslan --- src/blockchain/block_tree.hpp | 2 +- src/blockchain/impl/block_tree_impl.cpp | 2 +- src/blockchain/impl/block_tree_impl.hpp | 2 +- {tests/mock => src}/clock/manual_clock.hpp | 0 src/commands/test_driver.hpp | 400 +++++++++++------- src/executable/CMakeLists.txt | 4 - {tests/mock => src/metrics}/metrics_mock.hpp | 0 tests/mock/blockchain/block_tree_mock.hpp | 2 +- tests/test_vectors/fork_choice_test.cpp | 4 +- tests/test_vectors/state_transition_test.cpp | 2 +- tests/test_vectors/verify_signatures_test.cpp | 2 +- tests/unit/blockchain/fork_choice_test.cpp | 2 +- .../state_transition_function_test.cpp | 2 +- 13 files changed, 250 insertions(+), 174 deletions(-) rename {tests/mock => src}/clock/manual_clock.hpp (100%) rename {tests/mock => src/metrics}/metrics_mock.hpp (100%) diff --git a/src/blockchain/block_tree.hpp b/src/blockchain/block_tree.hpp index c03bc305..533c093c 100644 --- a/src/blockchain/block_tree.hpp +++ b/src/blockchain/block_tree.hpp @@ -169,7 +169,7 @@ namespace lean::blockchain { * "/leanconsensus/req/blocks_by_root/1/ssz_snappy" protocol. */ virtual outcome::result> tryGetSignedBlock( - const BlockHash block_hash) const = 0; + const BlockHash &block_hash) const = 0; }; } // namespace lean::blockchain diff --git a/src/blockchain/impl/block_tree_impl.cpp b/src/blockchain/impl/block_tree_impl.cpp index 4e95a005..c062b152 100644 --- a/src/blockchain/impl/block_tree_impl.cpp +++ b/src/blockchain/impl/block_tree_impl.cpp @@ -762,7 +762,7 @@ namespace lean::blockchain { } outcome::result> BlockTreeImpl::tryGetSignedBlock( - const BlockHash block_hash) const { + const BlockHash &block_hash) const { return block_tree_data_.sharedAccess( [&](const BlockTreeData &p) -> outcome::result> { diff --git a/src/blockchain/impl/block_tree_impl.hpp b/src/blockchain/impl/block_tree_impl.hpp index 13030c08..c102d650 100644 --- a/src/blockchain/impl/block_tree_impl.hpp +++ b/src/blockchain/impl/block_tree_impl.hpp @@ -108,7 +108,7 @@ namespace lean::blockchain { Checkpoint getLatestJustified() const override; outcome::result> tryGetSignedBlock( - const BlockHash block_hash) const override; + const BlockHash &block_hash) const override; // BlockHeaderRepository methods diff --git a/tests/mock/clock/manual_clock.hpp b/src/clock/manual_clock.hpp similarity index 100% rename from tests/mock/clock/manual_clock.hpp rename to src/clock/manual_clock.hpp diff --git a/src/commands/test_driver.hpp b/src/commands/test_driver.hpp index 9d6fa213..511635f6 100644 --- a/src/commands/test_driver.hpp +++ b/src/commands/test_driver.hpp @@ -11,26 +11,236 @@ #include #include +#include "app/chain_spec.hpp" +#include "blockchain/block_storage.hpp" #include "blockchain/fork_choice.hpp" #include "blockchain/impl/anchor_block_impl.hpp" #include "blockchain/impl/anchor_state_impl.hpp" +#include "blockchain/validator_registry.hpp" +#include "clock/manual_clock.hpp" #include "crypto/xmss/xmss_provider_impl.hpp" #include "log/logger.hpp" -#include "mock/app/chain_spec_mock.hpp" -#include "mock/app/configuration_mock.hpp" -#include "mock/app/validator_keys_manifest_mock.hpp" -#include "mock/blockchain/block_storage_mock.hpp" -#include "mock/blockchain/block_tree_mock.hpp" -#include "mock/blockchain/validator_registry_mock.hpp" -#include "mock/clock/manual_clock.hpp" -#include "mock/crypto/xmss_provider_mock.hpp" -#include "mock/metrics_mock.hpp" +#include "metrics/metrics_mock.hpp" #include "serde/json.hpp" #include "types/fork_choice_test_json.hpp" #include "types/signed_block.hpp" #include "types/state.hpp" #include "utils/http.hpp" +#define USING_(ns, name) using name = ns::name + +#define MOCK_UNUSED \ + override { \ + throw std::logic_error{ \ + fmt::format("unused mock function {}", __PRETTY_FUNCTION__)}; \ + } + +struct ValidatorRegistryMock : lean::ValidatorRegistry { + const ValidatorIndices ¤tValidatorIndices() const override { + return current_validator_indices_; + } + ValidatorIndices allValidatorsIndices() const MOCK_UNUSED; + std::optional nodeIdByIndex( + lean::ValidatorIndex index) const override { + return std::format("node_{}", index); + } + std::optional validatorIndicesForNodeId( + std::string_view node_id) const MOCK_UNUSED; + + ValidatorIndices current_validator_indices_{1}; +}; + +struct ChainSpecMock : lean::app::ChainSpec { + const lean::app::Bootnodes &getBootnodes() const MOCK_UNUSED; + bool isAggregator() const override { + return true; + } + bool setIsAggregator(bool is_aggregator) MOCK_UNUSED; +}; + +struct ConfigurationMock : lean::app::Configuration { + uint64_t cliSubnetCount() const override { + return 1; + } +}; + +struct ValidatorKeysManifestMock : lean::app::ValidatorKeysManifest { + std::optional getKeypair( + const lean::crypto::xmss::XmssPublicKey &public_key) const override { + return {}; + } + std::vector getAllXmssPubkeys() + const override { + return {}; + } +}; + +struct XmssProviderMock : lean::crypto::xmss::XmssProvider { + lean::crypto::xmss::XmssKeypair generateKeypair( + uint64_t activation_epoch, uint64_t num_active_epochs) MOCK_UNUSED; + lean::crypto::xmss::XmssSignature sign( + lean::crypto::xmss::XmssPrivateKey xmss_private_key, + uint32_t epoch, + const lean::crypto::xmss::XmssMessage &message) override { + return {}; + } + bool verify( + const lean::crypto::xmss::XmssPublicKey &xmss_public_key, + const lean::crypto::xmss::XmssMessage &message, + uint32_t epoch, + const lean::crypto::xmss::XmssSignature &xmss_signature) override { + return true; + } + lean::crypto::xmss::XmssAggregatedSignature aggregateSignatures( + std::span> + child_public_keys, + std::span child_proofs, + std::span public_keys, + std::span signatures, + uint32_t epoch, + const lean::crypto::xmss::XmssMessage &message) const override { + return {}; + } + bool verifyAggregatedSignatures( + std::span public_keys, + uint32_t epoch, + const lean::crypto::xmss::XmssMessage &message, + lean::crypto::xmss::XmssAggregatedSignatureIn aggregated_signature) + const override { + return true; + } +}; + +struct BlockTreeMock : lean::blockchain::BlockTree { + USING_(lean, BlockHash); + USING_(lean, BlockHeader); + USING_(lean, BlockIndex); + USING_(lean, BlockBody); + USING_(lean, SignedBlock); + USING_(lean, Checkpoint); + + // BlockHeaderRepository + outcome::result getSlotByHash( + const BlockHash &block_hash) const override { + return blocks_.at(block_hash).slot; + } + outcome::result getBlockHeader( + const BlockHash &block_hash) const override { + return blocks_.at(block_hash); + } + outcome::result> tryGetBlockHeader( + const BlockHash &block_hash) const override { + return blocks_.at(block_hash); + } + + // BlockTree + const BlockHash &getGenesisBlockHash() const MOCK_UNUSED; + bool has(const BlockHash &hash) const override { + return blocks_.contains(hash); + } + outcome::result getBlockBody(const BlockHash &) const MOCK_UNUSED; + outcome::result addBlockHeader(const BlockHeader &) MOCK_UNUSED; + outcome::result addBlockBody(const BlockHash &, + const BlockBody &) MOCK_UNUSED; + outcome::result addExistingBlock(const BlockHash &, + const BlockHeader &) MOCK_UNUSED; + outcome::result addBlock(SignedBlock block) override { + auto header = block.block.getHeader(); + header.updateHash(); + blocks_.emplace(header.hash(), header); + children_[header.parent_root].emplace_back(header.hash()); + return outcome::success(); + } + outcome::result removeLeaf(const BlockHash &) MOCK_UNUSED; + outcome::result finalize(const BlockHash &hash) override { + last_finalized_ = blocks_.at(hash).index(); + return outcome::success(); + } + outcome::result setJustified(const BlockHash &hash) override { + last_justified_ = blocks_.at(hash).index(); + return outcome::success(); + } + outcome::result> getBestChainFromBlock( + const BlockHash &, uint64_t) const MOCK_UNUSED; + outcome::result> getDescendingChainToBlock( + const BlockHash &, uint64_t) const MOCK_UNUSED; + bool isFinalized(const BlockIndex &) const MOCK_UNUSED; + BlockIndex bestBlock() const MOCK_UNUSED; + outcome::result getBestContaining(const BlockHash &) const + MOCK_UNUSED; + std::vector getLeaves() const MOCK_UNUSED; + outcome::result> getChildren( + const BlockHash &hash) const override { + std::vector children; + if (auto it = children_.find(hash); it != children_.end()) { + children = it->second; + } + return children; + } + BlockIndex lastFinalized() const override { + return last_finalized_; + } + Checkpoint getLatestJustified() const override { + return last_justified_; + } + outcome::result> tryGetSignedBlock( + const BlockHash &) const MOCK_UNUSED; + + lean::BlockIndex last_finalized_; + lean::BlockIndex last_justified_; + std::unordered_map blocks_; + std::unordered_map> children_; +}; + +struct BlockStorageMock : lean::blockchain::BlockStorage { + USING_(lean, BlockHash); + USING_(lean, BlockHeader); + USING_(lean, BlockBody); + USING_(lean, BlockData); + USING_(lean, BlockIndex); + USING_(lean, Slot); + USING_(lean, SignedBlock); + USING_(lean, State); + + // BlockStorage + outcome::result setBlockTreeLeaves(std::vector) MOCK_UNUSED; + outcome::result> getBlockTreeLeaves() const + MOCK_UNUSED; + outcome::result assignHashToSlot(const BlockIndex &) MOCK_UNUSED; + outcome::result deassignHashToSlot(const BlockIndex &) MOCK_UNUSED; + outcome::result> getBlockHash(Slot) const MOCK_UNUSED; + outcome::result seekLastSlot() const MOCK_UNUSED; + outcome::result hasBlockHeader(const BlockHash &) const MOCK_UNUSED; + outcome::result putBlockHeader(const BlockHeader &) MOCK_UNUSED; + outcome::result getBlockHeader(const BlockHash &) const + MOCK_UNUSED; + outcome::result> tryGetBlockHeader( + const BlockHash &) const MOCK_UNUSED; + outcome::result putBlockBody(const BlockHash &, + const BlockBody &) MOCK_UNUSED; + outcome::result> getBlockBody( + const BlockHash &) const MOCK_UNUSED; + outcome::result removeBlockBody(const BlockHash &) MOCK_UNUSED; + outcome::result putBlock(const BlockData &) MOCK_UNUSED; + outcome::result putState(const BlockHash &block_hash, + const State &state) override { + states_.emplace(block_hash, state); + return outcome::success(); + } + outcome::result> getState( + const BlockHash &block_hash) const override { + return states_.at(block_hash); + } + outcome::result removeState(const BlockHash &block_hash) MOCK_UNUSED; + outcome::result getBlock(const BlockHash &, + BlockParts) const MOCK_UNUSED; + outcome::result removeBlock(const BlockHash &) MOCK_UNUSED; + outcome::result getSignedBlock(const BlockHash &) const + MOCK_UNUSED; + + std::unordered_map states_; +}; + struct VerifySignaturesRequest { lean::State anchor_state; lean::SignedBlock signed_block; @@ -132,120 +342,37 @@ struct ForkChoiceDriver { ForkChoiceDriver(std::shared_ptr logsys, lean::State init_state) { - using testing::_; - anchor_state_ = std::make_shared(init_state); anchor_block_ = std::make_shared(*anchor_state_); - auto app_config = std::make_shared(); - EXPECT_CALL(*app_config, cliSubnetCount()) - .Times(testing::AnyNumber()) - .WillRepeatedly(testing::Return(1)); - - auto validator_registry = std::make_shared(); - EXPECT_CALL(*validator_registry, currentValidatorIndices()) - .Times(testing::AnyNumber()) - .WillRepeatedly(testing::ReturnRef(validator_indices_)); - EXPECT_CALL(*validator_registry, nodeIdByIndex(_)) - .WillRepeatedly( - [](lean::ValidatorIndex i) { return std::format("node_{}", i); }); - - auto chain_spec = std::make_shared(); - EXPECT_CALL(*chain_spec, isAggregator()) - .Times(testing::AnyNumber()) - .WillRepeatedly(testing::Return(true)); - - auto validator_key_manifest = - std::make_shared(); - EXPECT_CALL(*validator_key_manifest, getAllXmssPubkeys()) - .Times(testing::AnyNumber()); - EXPECT_CALL(*validator_key_manifest, getKeypair(_)) - .Times(testing::AnyNumber()) - .WillRepeatedly(testing::Return(std::nullopt)); - - auto xmss = std::make_shared(); - EXPECT_CALL(*xmss, verify(_, _, _, _)) - .WillRepeatedly(testing::Return(true)); - EXPECT_CALL(*xmss, sign(_, _, _)).Times(testing::AnyNumber()); - EXPECT_CALL(*xmss, verifyAggregatedSignatures(_, _, _, _)) - .WillRepeatedly(testing::Return(true)); - EXPECT_CALL(*xmss, aggregateSignatures(_, _, _, _, _, _)) - .Times(testing::AnyNumber()); - - auto block_tree = std::make_shared(); - last_finalized_ = anchor_block_->index(); - last_justified_ = last_finalized_; - blocks_.emplace(anchor_block_->hash(), *anchor_block_); - EXPECT_CALL(*block_tree, lastFinalized()).WillRepeatedly([&] { - return last_finalized_; - }); - EXPECT_CALL(*block_tree, finalize(_)).WillRepeatedly([&](BlockHash hash) { - last_finalized_ = blocks_.at(hash).index(); - return outcome::success(); - }); - EXPECT_CALL(*block_tree, getLatestJustified()).WillRepeatedly([&] { - return last_justified_; - }); - EXPECT_CALL(*block_tree, setJustified(_)) - .WillRepeatedly([&](BlockHash hash) { - last_justified_ = blocks_.at(hash).index(); - return outcome::success(); - }); - EXPECT_CALL(*block_tree, has(_)).WillRepeatedly([&](BlockHash hash) { - return blocks_.contains(hash); - }); - EXPECT_CALL(*block_tree, getSlotByHash(_)) - .WillRepeatedly([&](BlockHash hash) { return blocks_.at(hash).slot; }); - EXPECT_CALL(*block_tree, getBlockHeader(_)) - .WillRepeatedly([&](BlockHash hash) { return blocks_.at(hash); }); - EXPECT_CALL(*block_tree, tryGetBlockHeader(_)) - .WillRepeatedly([&](BlockHash hash) { return blocks_.at(hash); }); - EXPECT_CALL(*block_tree, getChildren(_)) - .WillRepeatedly([&](BlockHash hash) { return children_[hash]; }); - EXPECT_CALL(*block_tree, addBlock(_)) - .WillRepeatedly([&](lean::SignedBlock block) { - auto header = block.block.getHeader(); - header.updateHash(); - blocks_.emplace(header.hash(), header); - children_[header.parent_root].emplace_back(header.hash()); - return outcome::success(); - }); - - auto block_storage = std::make_shared(); - states_.emplace(anchor_block_->hash(), *anchor_state_); - EXPECT_CALL(*block_storage, getState(_)) - .WillRepeatedly([&](BlockHash hash) { return states_.at(hash); }); - EXPECT_CALL(*block_storage, putState(_, _)) - .WillRepeatedly([&](BlockHash hash, lean::State state) { - states_.emplace(hash, state); - return outcome::success(); - }); + auto block_tree = std::make_shared(); + block_tree->last_finalized_ = anchor_block_->index(); + block_tree->last_justified_ = block_tree->last_finalized_; + block_tree->blocks_.emplace(anchor_block_->hash(), *anchor_block_); + + auto block_storage = std::make_shared(); + block_storage->states_.emplace(anchor_block_->hash(), *anchor_state_); store_.emplace(anchor_state_, anchor_block_, std::make_shared(), logsys, std::make_shared(), - app_config, - validator_registry, - chain_spec, - validator_key_manifest, - xmss, + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared(), block_tree, block_storage); store_->dontPropose(); } lean::ValidatorRegistry::ValidatorIndices validator_indices_{0}; - lean::BlockIndex last_finalized_; - lean::BlockIndex last_justified_; std::shared_ptr anchor_state_; std::shared_ptr anchor_block_; - std::unordered_map blocks_; - std::unordered_map> children_; - std::unordered_map states_; std::optional store_; }; @@ -270,17 +397,10 @@ inline int cmdTestDriver(std::shared_ptr logsys, request, [&](VerifySignaturesRequest request) { lean::ValidatorRegistry::ValidatorIndices validator_indices{ 0}; - auto validator_registry = - std::make_shared(); - EXPECT_CALL(*validator_registry, currentValidatorIndices()) - .Times(testing::AnyNumber()) - .WillRepeatedly(testing::ReturnRef(validator_indices)); - auto block_storage = - std::make_shared(); - EXPECT_CALL( - *block_storage, - getState(request.signed_block.block.parent_root)) - .WillOnce(testing::Return(request.anchor_state)); + auto block_storage = std::make_shared(); + block_storage->states_.emplace( + request.signed_block.block.parent_root, + request.anchor_state); lean::ForkChoiceStore store{ {}, logsys, @@ -291,12 +411,11 @@ inline int cmdTestDriver(std::shared_ptr logsys, {}, {}, 0, - validator_registry, - std::make_shared< - lean::app::ValidatorKeysManifestMock>(), + std::make_shared(), + std::make_shared(), std::make_shared< lean::crypto::xmss::XmssProviderImpl>(), - std::make_shared(), + std::make_shared(), block_storage, false, 1, @@ -310,15 +429,9 @@ inline int cmdTestDriver(std::shared_ptr logsys, if (url == "/lean/v0/test_driver/state_transition/run") { return httpJson( request, [&](StateTransitionRequest request) { - auto block_tree = - std::make_shared(); - EXPECT_CALL(*block_tree, getLatestJustified()) - .Times(testing::AnyNumber()); - EXPECT_CALL(*block_tree, lastFinalized()) - .Times(testing::AnyNumber()); lean::STF stf{ logsys, - block_tree, + std::make_shared(), std::make_shared(), }; auto stf_many = [&]() -> outcome::result { @@ -406,39 +519,6 @@ inline int cmdTestDriver(std::shared_ptr logsys, store.onTick(block_time); return store.onBlock(signed_block); }(); - if (auto &checks = block_step->checks) { - if (checks->block_attestation_count) { - EXPECT_EQ(block_step->block.body.attestations.size(), - *checks->block_attestation_count); - } - if (checks->block_attestations) { - auto &attestations = - block_step->block.body.attestations.data(); - for (auto &check : *checks->block_attestations) { - lean::AggregationBits participants; - for (auto &validator : check.participants) { - participants.add(validator); - } - auto attestation_it = std::ranges::find_if( - attestations, - [&](const lean::AggregatedAttestation - &attestation) { - return attestation.aggregation_bits - == participants; - }); - EXPECT_NE(attestation_it, attestations.end()); - auto &attestation = *attestation_it; - if (check.attestation_slot) { - EXPECT_EQ(attestation.data.slot, - *check.attestation_slot); - } - if (check.target_slot) { - EXPECT_EQ(attestation.data.target.slot, - *check.target_slot); - } - } - } - } } else if (auto *attestation_step = std::get_if( &request.v)) { @@ -480,7 +560,7 @@ inline int cmdTestDriver(std::shared_ptr logsys, response.result(boost::beast::http::status::not_found); return response; }, - .max_request_size = 1 << 20, + .max_request_size = 16 << 20, }; boost::asio::io_context io_context; if (auto res = lean::http::serve(log, io_context, config_api); diff --git a/src/executable/CMakeLists.txt b/src/executable/CMakeLists.txt index 841a4673..dc24b629 100644 --- a/src/executable/CMakeLists.txt +++ b/src/executable/CMakeLists.txt @@ -48,10 +48,6 @@ endif () set_target_properties(lean_node PROPERTIES OUTPUT_NAME qlean) add_dependencies(lean_node all_modules) -find_package(GTest CONFIG REQUIRED) -target_include_directories(lean_node PRIVATE ${PROJECT_SOURCE_DIR}/tests) -target_link_libraries(lean_node GTest::gmock) - #if (BACKWARD) # add_backward(lean_node) #endif () diff --git a/tests/mock/metrics_mock.hpp b/src/metrics/metrics_mock.hpp similarity index 100% rename from tests/mock/metrics_mock.hpp rename to src/metrics/metrics_mock.hpp diff --git a/tests/mock/blockchain/block_tree_mock.hpp b/tests/mock/blockchain/block_tree_mock.hpp index af3b57ac..4c2ad899 100644 --- a/tests/mock/blockchain/block_tree_mock.hpp +++ b/tests/mock/blockchain/block_tree_mock.hpp @@ -104,7 +104,7 @@ namespace lean::blockchain { MOCK_METHOD(outcome::result>, tryGetSignedBlock, - (const BlockHash block_hash), + (const BlockHash &), (const, override)); }; diff --git a/tests/test_vectors/fork_choice_test.cpp b/tests/test_vectors/fork_choice_test.cpp index d51f56c5..cd09b4b5 100644 --- a/tests/test_vectors/fork_choice_test.cpp +++ b/tests/test_vectors/fork_choice_test.cpp @@ -8,15 +8,15 @@ #include "blockchain/impl/anchor_block_impl.hpp" #include "blockchain/impl/anchor_state_impl.hpp" +#include "clock/manual_clock.hpp" +#include "metrics/metrics_mock.hpp" #include "mock/app/chain_spec_mock.hpp" #include "mock/app/configuration_mock.hpp" #include "mock/app/validator_keys_manifest_mock.hpp" #include "mock/blockchain/block_storage_mock.hpp" #include "mock/blockchain/block_tree_mock.hpp" #include "mock/blockchain/validator_registry_mock.hpp" -#include "mock/clock/manual_clock.hpp" #include "mock/crypto/xmss_provider_mock.hpp" -#include "mock/metrics_mock.hpp" #include "test_vectors.hpp" #include "testutil/prepare_loggers.hpp" #include "types/fork_choice_test_json.hpp" diff --git a/tests/test_vectors/state_transition_test.cpp b/tests/test_vectors/state_transition_test.cpp index 55e4523d..8cbcf67b 100644 --- a/tests/test_vectors/state_transition_test.cpp +++ b/tests/test_vectors/state_transition_test.cpp @@ -5,8 +5,8 @@ */ #include "blockchain/state_transition_function.hpp" +#include "metrics/metrics_mock.hpp" #include "mock/blockchain/block_tree_mock.hpp" -#include "mock/metrics_mock.hpp" #include "state_transition_test_json.hpp" #include "test_vectors.hpp" #include "testutil/prepare_loggers.hpp" diff --git a/tests/test_vectors/verify_signatures_test.cpp b/tests/test_vectors/verify_signatures_test.cpp index e371fb47..69ced95b 100644 --- a/tests/test_vectors/verify_signatures_test.cpp +++ b/tests/test_vectors/verify_signatures_test.cpp @@ -6,11 +6,11 @@ #include "blockchain/fork_choice.hpp" #include "crypto/xmss/xmss_provider_impl.hpp" +#include "metrics/metrics_mock.hpp" #include "mock/app/validator_keys_manifest_mock.hpp" #include "mock/blockchain/block_storage_mock.hpp" #include "mock/blockchain/block_tree_mock.hpp" #include "mock/blockchain/validator_registry_mock.hpp" -#include "mock/metrics_mock.hpp" #include "test_vectors.hpp" #include "testutil/prepare_loggers.hpp" #include "verify_signatures_test_json.hpp" diff --git a/tests/unit/blockchain/fork_choice_test.cpp b/tests/unit/blockchain/fork_choice_test.cpp index 23f56e8a..42736755 100644 --- a/tests/unit/blockchain/fork_choice_test.cpp +++ b/tests/unit/blockchain/fork_choice_test.cpp @@ -13,12 +13,12 @@ #include "blockchain/block_tree.hpp" #include "blockchain/is_justifiable_slot.hpp" #include "blockchain/state_transition_function.hpp" +#include "metrics/metrics_mock.hpp" #include "mock/app/validator_keys_manifest_mock.hpp" #include "mock/blockchain/block_storage_mock.hpp" #include "mock/blockchain/block_tree_mock.hpp" #include "mock/blockchain/validator_registry_mock.hpp" #include "mock/crypto/xmss_provider_mock.hpp" -#include "mock/metrics_mock.hpp" #include "qtils/test/outcome.hpp" #include "testutil/prepare_loggers.hpp" #include "types/attestation.hpp" diff --git a/tests/unit/blockchain/state_transition_function_test.cpp b/tests/unit/blockchain/state_transition_function_test.cpp index 058de8e9..1f9bd104 100644 --- a/tests/unit/blockchain/state_transition_function_test.cpp +++ b/tests/unit/blockchain/state_transition_function_test.cpp @@ -10,8 +10,8 @@ #include "blockchain/impl/anchor_block_impl.hpp" #include "blockchain/impl/anchor_state_impl.hpp" +#include "metrics/metrics_mock.hpp" #include "mock/blockchain/block_tree_mock.hpp" -#include "mock/metrics_mock.hpp" #include "testutil/prepare_loggers.hpp" #include "types/config.hpp" #include "types/state.hpp" From a8191546a01090ec6ee8dca2cc77702392f07c32 Mon Sep 17 00:00:00 2001 From: turuslan Date: Wed, 20 May 2026 09:54:24 +0500 Subject: [PATCH 07/12] /lean/v0/blocks/finalized Signed-off-by: turuslan --- src/app/impl/http_server.cpp | 22 ++++++++++++++++++++++ src/app/impl/http_server.hpp | 6 ++++++ 2 files changed, 28 insertions(+) diff --git a/src/app/impl/http_server.cpp b/src/app/impl/http_server.cpp index a2f4e96c..e0cce1b3 100644 --- a/src/app/impl/http_server.cpp +++ b/src/app/impl/http_server.cpp @@ -14,6 +14,7 @@ #include "app/chain_spec.hpp" #include "app/configuration.hpp" #include "app/state_manager.hpp" +#include "blockchain/block_tree.hpp" #include "blockchain/fork_choice_mutex.hpp" #include "metrics/handler.hpp" #include "serde/json.hpp" @@ -35,11 +36,13 @@ namespace lean::app { qtils::SharedRef app_config, qtils::SharedRef metrics_handler, qtils::SharedRef chain_spec, + qtils::SharedRef block_tree, qtils::SharedRef fork_choice_store) : log_{logsys->getLogger("HttpServer", "http")}, app_config_{std::move(app_config)}, metrics_handler_{std::move(metrics_handler)}, chain_spec_{std::move(chain_spec)}, + block_tree_{std::move(block_tree)}, fork_choice_store_{std::move(fork_choice_store)} { state_manager->takeControl(*this); } @@ -149,6 +152,25 @@ namespace lean::app { previous)); } } + if (url == "/lean/v0/blocks/finalized") { + auto finalized = self->fork_choice_store_->getLatestFinalized(); + auto block_result = + self->block_tree_->tryGetSignedBlock(finalized.root); + if (not block_result.has_value()) { + response.result( + boost::beast::http::status::internal_server_error); + return response; + } + auto &block = block_result.value(); + if (not block.has_value()) { + response.result(boost::beast::http::status::not_found); + return response; + } + response.set(boost::beast::http::field::content_type, + "application/octet-stream"); + response.body() = qtils::byte2str(encode(*block).value()); + return response; + } response.result(boost::beast::http::status::not_found); return response; }, diff --git a/src/app/impl/http_server.hpp b/src/app/impl/http_server.hpp index b320fe61..eafb71b7 100644 --- a/src/app/impl/http_server.hpp +++ b/src/app/impl/http_server.hpp @@ -24,6 +24,10 @@ namespace lean::app { class ChainSpec; } // namespace lean::app +namespace lean::blockchain { + class BlockTree; +} // namespace lean::blockchain + namespace lean::metrics { class Handler; } // namespace lean::metrics @@ -39,6 +43,7 @@ namespace lean::app { qtils::SharedRef app_config, qtils::SharedRef metrics_handler, qtils::SharedRef chain_spec, + qtils::SharedRef block_tree, qtils::SharedRef fork_choice_store); ~HttpServer(); @@ -50,6 +55,7 @@ namespace lean::app { qtils::SharedRef app_config_; qtils::SharedRef metrics_handler_; qtils::SharedRef chain_spec_; + qtils::SharedRef block_tree_; qtils::SharedRef fork_choice_store_; std::shared_ptr io_context_; std::optional io_thread_; From 891723629b7bb497df48486d0807eb96e6932fd4 Mon Sep 17 00:00:00 2001 From: turuslan Date: Mon, 25 May 2026 09:01:00 +0500 Subject: [PATCH 08/12] don't stop loop Signed-off-by: turuslan --- src/blockchain/fork_choice.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blockchain/fork_choice.cpp b/src/blockchain/fork_choice.cpp index 50f652eb..162317d8 100644 --- a/src/blockchain/fork_choice.cpp +++ b/src/blockchain/fork_choice.cpp @@ -491,12 +491,12 @@ namespace lean { } auto attestations_it = attestations_by_data_.find(sszHash(data)); if (attestations_it == attestations_by_data_.end()) { - break; + continue; } auto &attestations = attestations_it->second; // TODO(zeam): producer may aggregate if (attestations.proofs.empty()) { - break; + continue; } for (auto &proof : attestations.proofs) { From f81035a806435f9af9c8eeeb6746bd1c81b0e319 Mon Sep 17 00:00:00 2001 From: turuslan Date: Mon, 25 May 2026 09:07:16 +0500 Subject: [PATCH 09/12] propose MAX_ATTESTATIONS_DATA Signed-off-by: turuslan --- src/blockchain/fork_choice.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/blockchain/fork_choice.cpp b/src/blockchain/fork_choice.cpp index 162317d8..7f46e8b1 100644 --- a/src/blockchain/fork_choice.cpp +++ b/src/blockchain/fork_choice.cpp @@ -485,7 +485,11 @@ namespace lean { AggregatedAttestations aggregated_attestations; AttestationSignatures aggregated_proofs; auto expected_source = head_state->latest_justified; + size_t processed_att_data = 0; for (auto &data : sorted_data) { + if (processed_att_data >= MAX_ATTESTATIONS_DATA) { + break; + } if (data.source != expected_source) { continue; } @@ -499,6 +503,7 @@ namespace lean { continue; } + ++processed_att_data; for (auto &proof : attestations.proofs) { aggregated_attestations.push_back({ .aggregation_bits = proof.participants, From 203e523ff63ec817b51530dfe6f91b3f0c2d0c53 Mon Sep 17 00:00:00 2001 From: turuslan Date: Mon, 25 May 2026 10:23:08 +0500 Subject: [PATCH 10/12] propose metrics Signed-off-by: turuslan --- src/blockchain/block_production_metrics.def | 26 ++++ src/blockchain/fork_choice.cpp | 146 +++++++++++++++----- src/blockchain/fork_choice.hpp | 8 ++ src/metrics/metrics.hpp | 9 ++ 4 files changed, 153 insertions(+), 36 deletions(-) diff --git a/src/blockchain/block_production_metrics.def b/src/blockchain/block_production_metrics.def index 2e30b225..fd9541cc 100644 --- a/src/blockchain/block_production_metrics.def +++ b/src/blockchain/block_production_metrics.def @@ -25,3 +25,29 @@ METRIC_COUNTER(lean_block_building_success_total, METRIC_COUNTER(lean_block_building_failures_total, "lean_block_building_failures_total", "Failed block builds (exception in build_block)"); + +// https://github.com/leanEthereum/leanSpec/pull/753 +// phase = "select_payloads" | "compact" | "stf_simulate" +METRIC_HISTOGRAM_LABELS( + lean_block_proposal_attestation_build_phase_seconds, + "lean_block_proposal_attestation_build_phase_seconds", + "Phase-level time in block-proposal attestation selection: select_payloads (greedy child-payload pick), compact (recursive merge of proofs per AttestationData), stf_simulate (candidate block STF).", + (0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 4, 8), + ({"phase"})); +METRIC_COUNTER( + lean_block_proposal_attestation_builds_total, + "lean_block_proposal_attestation_builds_total", + "Completed block-proposal attestation selection runs (one per proposal attempt)."); +METRIC_COUNTER( + lean_block_proposal_child_payloads_consumed_total, + "lean_block_proposal_child_payloads_consumed_total", + "Child aggregated payloads selected during greedy proof picking (before recursive compaction)."); +METRIC_HISTOGRAM(lean_block_proposal_attestation_data_selected, + "lean_block_proposal_attestation_data_selected", + "Distinct AttestationData entries in the proposal block body.", + (0, 1, 2, 4, 8, 16, 32)); +METRIC_HISTOGRAM( + lean_block_proposal_aggregates_selected, + "lean_block_proposal_aggregates_selected", + "Aggregated signature proofs in the proposal result after compaction.", + (0, 1, 2, 4, 8, 16, 32, 64, 128)); diff --git a/src/blockchain/fork_choice.cpp b/src/blockchain/fork_choice.cpp index 7f46e8b1..3a4b12e9 100644 --- a/src/blockchain/fork_choice.cpp +++ b/src/blockchain/fork_choice.cpp @@ -414,6 +414,13 @@ namespace lean { BOOST_OUTCOME_TRY(auto aggregated, getProposalAttestations(slot, proposer_index, head_root)); + metrics_->lean_block_proposal_attestation_builds_total()->inc(); + + aggregated = + aggregateDuplicate(*head_state, aggregated.first, aggregated.second); + + metrics_->lean_block_proposal_aggregates_selected()->observe( + aggregated.second.size()); // Create the final block with all collected attestations Block block{ @@ -487,6 +494,11 @@ namespace lean { auto expected_source = head_state->latest_justified; size_t processed_att_data = 0; for (auto &data : sorted_data) { + auto time_select = + metrics_ + ->lean_block_proposal_attestation_build_phase_seconds( + {{"phase", "select_payloads"}}) + ->timerManual(); if (processed_att_data >= MAX_ATTESTATIONS_DATA) { break; } @@ -512,6 +524,11 @@ namespace lean { aggregated_proofs.push_back(proof); } + time_select(); + auto time_stf = metrics_ + ->lean_block_proposal_attestation_build_phase_seconds( + {{"phase", "stf_simulate"}}) + ->timer(); auto post_state = *head_state; BOOST_OUTCOME_TRY(stf_.processSlots(post_state, slot)); BOOST_OUTCOME_TRY(stf_.processBlock( @@ -523,11 +540,56 @@ namespace lean { .state_root = {}, .body = {.attestations = aggregated_attestations}, })); + time_stf.stop(); expected_source = post_state.latest_justified; } + metrics_->lean_block_proposal_attestation_data_selected()->observe( + processed_att_data); + metrics_->lean_block_proposal_child_payloads_consumed_total()->inc( + aggregated_attestations.size()); return std::make_pair(aggregated_attestations, aggregated_proofs); } + std::pair + ForkChoiceStore::aggregateDuplicate( + const State &state, + const AggregatedAttestations &attestations, + const AttestationSignatures &signatures) { + auto metric_time = + metrics_ + ->lean_block_proposal_attestation_build_phase_seconds( + {{"phase", "compact"}}) + ->timer(); + using Group = + std::pair>; + std::unordered_map groups; + for (auto &&[attestation, proof] : + std::views::zip(attestations, signatures)) { + auto key = sszHash(attestation.data); + auto it = groups.find(key); + if (it == groups.end()) { + it = groups.emplace(key, Group{attestation.data, {}}).first; + } + it->second.second.emplace_back(proof); + } + AggregatedAttestations new_attestations; + AttestationSignatures new_signatures; + for (auto &[data, proofs] : groups | std::views::values) { + auto proof = proofs.at(0); + if (proofs.size() != 1) { + proof = aggregateSignatures( + state, data, std::map{}, proofs) + .proof; + } + new_attestations.push_back({ + .aggregation_bits = proof.participants, + .data = data, + }); + new_signatures.push_back(proof); + } + return std::make_pair(new_attestations, new_signatures); + } + outcome::result ForkChoiceStore::validateAttestation( const Attestation &attestation) { auto has_block = [&](const BlockHash &hash) { @@ -1415,48 +1477,60 @@ namespace lean { continue; } auto &state = *state_res.value(); - std::vector> child_public_keys; - std::vector child_proofs; + auto aggregated = aggregateSignatures(state, + attestations.data, + attestations.signatures, + attestations.proofs); + aggregated_attestations.emplace_back(aggregated); + attestations.signatures.clear(); + attestations.proofs.clear(); + attestations.proofs.emplace_back(aggregated.proof); + } + return aggregated_attestations; + } + + SignedAggregatedAttestation ForkChoiceStore::aggregateSignatures( + const State &state, + const AttestationData &data, + const auto &signatures_in, + const auto &proofs_in) { + std::vector> child_public_keys; + std::vector child_proofs; + std::vector public_keys; + std::vector signatures; + AggregationBits participants; + for (auto &[validator_id, signature] : signatures_in) { + public_keys.emplace_back( + state.validators.data().at(validator_id).attestation_pubkey); + signatures.emplace_back(signature); + participants.add(validator_id); + } + for (auto &proof : proofs_in) { std::vector public_keys; - std::vector signatures; - AggregationBits participants; - for (auto &[validator_id, signature] : attestations.signatures) { + for (auto &&validator_id : proof.participants.iter()) { public_keys.emplace_back( state.validators.data().at(validator_id).attestation_pubkey); - signatures.emplace_back(signature); participants.add(validator_id); } - for (auto &proof : attestations.proofs) { - std::vector public_keys; - for (auto &&validator_id : proof.participants.iter()) { - public_keys.emplace_back( - state.validators.data().at(validator_id).attestation_pubkey); - participants.add(validator_id); - } - child_public_keys.emplace_back(std::move(public_keys)); - child_proofs.emplace_back(proof.proof_data); - } - auto payload = attestationPayload(attestations.data); - auto aggregated_signature = - xmss_provider_->aggregateSignatures(child_public_keys, - child_proofs, - public_keys, - signatures, - attestations.data.slot, - payload); - AggregatedSignatureProof proof{ - .participants = participants, - .proof_data = aggregated_signature, - }; - aggregated_attestations.emplace_back(SignedAggregatedAttestation{ - .data = attestations.data, - .proof = proof, - }); - attestations.signatures.clear(); - attestations.proofs.clear(); - attestations.proofs.emplace_back(proof); + child_public_keys.emplace_back(std::move(public_keys)); + child_proofs.emplace_back(proof.proof_data); } - return aggregated_attestations; + auto payload = attestationPayload(data); + auto aggregated_signature = + xmss_provider_->aggregateSignatures(child_public_keys, + child_proofs, + public_keys, + signatures, + data.slot, + payload); + return SignedAggregatedAttestation{ + .data = data, + .proof = + { + .participants = participants, + .proof_data = aggregated_signature, + }, + }; } void ForkChoiceStore::prune(Slot finalized_slot) { diff --git a/src/blockchain/fork_choice.hpp b/src/blockchain/fork_choice.hpp index f112f549..ca06d225 100644 --- a/src/blockchain/fork_choice.hpp +++ b/src/blockchain/fork_choice.hpp @@ -306,6 +306,10 @@ namespace lean { getProposalAttestations(Slot slot, ValidatorIndex proposer_index, BlockHash parent_root); + std::pair aggregateDuplicate( + const State &state, + const AggregatedAttestations &attestations, + const AttestationSignatures &signatures); /** * Produce an attestation for the given slot and validator. @@ -486,6 +490,10 @@ namespace lean { const AggregatedSignatureProof &signature) const; std::vector aggregateSignatures(); + SignedAggregatedAttestation aggregateSignatures(const State &state, + const AttestationData &data, + const auto &signatures_in, + const auto &proofs_in); void prune(Slot finalized_slot); void updateMetricGossipSignatures(); diff --git a/src/metrics/metrics.hpp b/src/metrics/metrics.hpp index 89383949..32b0a237 100644 --- a/src/metrics/metrics.hpp +++ b/src/metrics/metrics.hpp @@ -126,6 +126,8 @@ namespace lean::metrics { * @return HistogramTimer that records duration when it goes out of scope */ HistogramTimer timer(); + + auto timerManual(); }; /** @@ -276,4 +278,11 @@ namespace lean::metrics { return HistogramTimer(this); } + inline auto Histogram::timerManual() { + return [this, start_time{HistogramTimer::Clock::now()}] { + HistogramTimer::Duration elapsed = + HistogramTimer::Clock::now() - start_time; + observe(elapsed.count()); + }; + } } // namespace lean::metrics From c98f5c73b8f5c923c85083c82b74ef2037c8e794 Mon Sep 17 00:00:00 2001 From: turuslan Date: Mon, 25 May 2026 13:26:36 +0500 Subject: [PATCH 11/12] comment Signed-off-by: turuslan --- src/blockchain/fork_choice.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/blockchain/fork_choice.hpp b/src/blockchain/fork_choice.hpp index ca06d225..3673f192 100644 --- a/src/blockchain/fork_choice.hpp +++ b/src/blockchain/fork_choice.hpp @@ -306,6 +306,11 @@ namespace lean { getProposalAttestations(Slot slot, ValidatorIndex proposer_index, BlockHash parent_root); + /** + * Aggregate duplicate attestations referencing same data. + * Groups aggregated attestations by data and recursively aggregates their + * proofs. + */ std::pair aggregateDuplicate( const State &state, const AggregatedAttestations &attestations, From 6d633df6ffcf8f4a737e100fff5045318e101561 Mon Sep 17 00:00:00 2001 From: turuslan Date: Mon, 25 May 2026 13:28:09 +0500 Subject: [PATCH 12/12] fix latest_block_header_state_root Signed-off-by: turuslan --- src/commands/test_driver.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/test_driver.hpp b/src/commands/test_driver.hpp index 511635f6..0caa9631 100644 --- a/src/commands/test_driver.hpp +++ b/src/commands/test_driver.hpp @@ -458,7 +458,7 @@ inline int cmdTestDriver(std::shared_ptr logsys, .latest_block_header_slot = state.latest_block_header.slot, .latest_block_header_state_root = - state.latest_block_header.hash(), + state.latest_block_header.state_root, .historical_block_hashes_count = state.historical_block_hashes.size(), },