diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index beac45b..571d33e 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -57,11 +57,11 @@ jobs: uses: actions/cache/restore@v4 with: path: ${{ env.CACHE_PATH }} - key: jam-${{ runner.os }}-${{ github.job }}-${{ env.CACHE_VERSION }}-${{ github.run_id }} + key: lean-${{ runner.os }}-${{ github.job }}-${{ env.CACHE_VERSION }}-${{ github.run_id }} restore-keys: | - jam-${{ runner.os }}-${{ github.job }}-${{ env.CACHE_VERSION }} - jam-${{ runner.os }}-${{ github.job }} - jam-${{ runner.os }} + lean-${{ runner.os }}-${{ github.job }}-${{ env.CACHE_VERSION }} + lean-${{ runner.os }}-${{ github.job }} + lean-${{ runner.os }} - name: "Basic init" run: | diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 04bf1e6..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "test-vectors/jamtestvectors"] - path = test-vectors/jamtestvectors - url = https://github.com/davxy/jam-test-vectors diff --git a/CMakeLists.txt b/CMakeLists.txt index e9035fd..9a9edbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ message(STATUS "Boost_DIR: ${Boost_DIR}") find_package(Python3 REQUIRED) find_package(PkgConfig REQUIRED) -pkg_check_modules(libb2 REQUIRED IMPORTED_TARGET GLOBAL libb2) +#pkg_check_modules(libb2 REQUIRED IMPORTED_TARGET GLOBAL libb2) find_package(Boost CONFIG REQUIRED COMPONENTS algorithm outcome program_options property_tree) find_package(fmt CONFIG REQUIRED) diff --git a/docker/ci-docker b/docker/ci-docker index 3ba1a4a..51331a9 100755 --- a/docker/ci-docker +++ b/docker/ci-docker @@ -9,7 +9,7 @@ # paths -PROJECT=/cpp-jam +PROJECT=/cpp-lean VENV=$PROJECT.venv BUILD=$PROJECT.build VCPKG=/vcpkg diff --git a/docker/ci-docker-run b/docker/ci-docker-run index 74c79a0..929ff8c 100644 --- a/docker/ci-docker-run +++ b/docker/ci-docker-run @@ -5,5 +5,5 @@ LOCAL=$PWD [[ -d $LOCAL/docker ]] docker run --rm -it \ - -v $LOCAL:/cpp-jam \ - ubuntu:24.04 /cpp-jam/docker/ci-docker + -v $LOCAL:/cpp-lean \ + ubuntu:24.04 /cpp-lean/docker/ci-docker diff --git a/example/config.yaml b/example/config.yaml index 0fbba9d..1b88755 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -27,13 +27,14 @@ logging: level: trace is_fallback: true children: - - name: jam + - name: lean children: - name: modules children: - name: example_module - name: synchronizer_module - name: networking_module + - name: production_module - name: injector - name: application - name: rpc diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 12cd652..b103d97 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -59,6 +59,7 @@ target_link_libraries(chain_spec logger Boost::property_tree app_configuration + sszpp ) add_library(application SHARED @@ -69,3 +70,14 @@ target_link_libraries(application app_configuration metrics ) + +add_library(timeline SHARED + impl/timeline_impl.cpp +) +target_link_libraries(timeline + qtils::qtils + logger + sszpp +# app_configuration +# metrics +) diff --git a/src/app/chain_spec.hpp b/src/app/chain_spec.hpp index 173327e..63b277a 100644 --- a/src/app/chain_spec.hpp +++ b/src/app/chain_spec.hpp @@ -11,7 +11,7 @@ #include -#include "lean_types/types.hpp" +#include "types/types.hpp" namespace lean { diff --git a/src/app/impl/application_impl.cpp b/src/app/impl/application_impl.cpp index e805202..99c152e 100644 --- a/src/app/impl/application_impl.cpp +++ b/src/app/impl/application_impl.cpp @@ -12,6 +12,7 @@ #include "app/configuration.hpp" #include "app/impl/watchdog.hpp" #include "app/state_manager.hpp" +#include "app/timeline.hpp" #include "clock/clock.hpp" #include "log/logger.hpp" #include "metrics/histogram_timer.hpp" @@ -33,6 +34,7 @@ namespace lean::app { qtils::SharedRef watchdog, qtils::SharedRef metrics_exposer, qtils::SharedRef system_clock, + qtils::SharedRef timeline, std::shared_ptr) : logger_(logsys->getLogger("Application", "application")), app_config_(std::move(config)), @@ -40,6 +42,7 @@ namespace lean::app { watchdog_(std::move(watchdog)), metrics_exposer_(std::move(metrics_exposer)), system_clock_(std::move(system_clock)), + timeline_(std::move(timeline)), metrics_registry_(metrics::createRegistry()) { // Metric for exposing name and version of node metrics::GaugeHelper( diff --git a/src/app/impl/application_impl.hpp b/src/app/impl/application_impl.hpp index a86bbf3..818a556 100644 --- a/src/app/impl/application_impl.hpp +++ b/src/app/impl/application_impl.hpp @@ -19,6 +19,7 @@ namespace lean { } // namespace lean namespace lean::app { + class Timeline; class Configuration; class StateManager; } // namespace lean::app @@ -82,6 +83,7 @@ namespace lean::app { qtils::SharedRef watchdog, qtils::SharedRef metrics_exposer, qtils::SharedRef system_clock, + qtils::SharedRef timeline, std::shared_ptr); void run() override; @@ -93,6 +95,7 @@ namespace lean::app { qtils::SharedRef watchdog_; qtils::SharedRef metrics_exposer_; qtils::SharedRef system_clock_; + qtils::SharedRef timeline_; // Metrics std::unique_ptr metrics_registry_; diff --git a/src/app/impl/chain_spec_impl.cpp b/src/app/impl/chain_spec_impl.cpp index b3686d8..8660053 100644 --- a/src/app/impl/chain_spec_impl.cpp +++ b/src/app/impl/chain_spec_impl.cpp @@ -12,6 +12,8 @@ #include +#include "types/block_header.hpp" + OUTCOME_CPP_DEFINE_CATEGORY(lean::app, ChainSpecImpl::Error, e) { using E = lean::app::ChainSpecImpl::Error; switch (e) { @@ -68,11 +70,16 @@ namespace lean::app { outcome::result ChainSpecImpl::loadGenesis( const boost::property_tree::ptree &tree) { - OUTCOME_TRY( - genesis_header_hex, - ensure("genesis_header", tree.get_child_optional("genesis_header"))); - OUTCOME_TRY(genesis_header_encoded, - qtils::ByteVec::fromHex(genesis_header_hex.data())); + // OUTCOME_TRY( + // genesis_header_hex, + // ensure("genesis_header", tree.get_child_optional("genesis_header"))); + // OUTCOME_TRY(genesis_header_encoded, + // qtils::ByteVec::fromHex(genesis_header_hex.data())); + + BlockHeader header; + header.proposer_index = -1ull; + OUTCOME_TRY(genesis_header_encoded, encode(header)); + genesis_header_ = std::move(genesis_header_encoded); return outcome::success(); } diff --git a/src/app/impl/timeline_impl.cpp b/src/app/impl/timeline_impl.cpp new file mode 100644 index 0000000..13d86c2 --- /dev/null +++ b/src/app/impl/timeline_impl.cpp @@ -0,0 +1,93 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include "timeline_impl.hpp" + +#include + +#include "app/state_manager.hpp" +#include "clock/clock.hpp" +#include "log/logger.hpp" +#include "modules/shared/prodution_types.tmp.hpp" +#include "se/impl/subscription_manager.hpp" +#include "se/subscription.hpp" +#include "se/subscription_fwd.hpp" +#include "types/config.hpp" +#include "types/constants.hpp" + +namespace lean::app { + + TimelineImpl::TimelineImpl(qtils::SharedRef logsys, + qtils::SharedRef state_manager, + qtils::SharedRef se_manager, + qtils::SharedRef clock, + qtils::SharedRef config) + : 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)) { + state_manager_->takeControl(*this); + } + + void TimelineImpl::prepare() { + on_slot_started_ = + se::SubscriberCreator>:: + create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [this](auto &, + std::shared_ptr msg) { + on_slot_started(std::move(msg)); + }); + } + + void TimelineImpl::start() { + auto now = clock_->nowMsec(); + auto next_slot = (now - config_->genesis_time) / SLOT_DURATION_MS + 1; + auto time_to_next_slot = + config_->genesis_time + SLOT_DURATION_MS * next_slot - now; + if (time_to_next_slot < SLOT_DURATION_MS / 2) { + ++next_slot; + time_to_next_slot += SLOT_DURATION_MS; + } + SL_INFO(logger_, + "Starting timeline. Next slot is {}, starts in {}ms", + next_slot, + time_to_next_slot); + se_manager_->notifyDelayed( + std::chrono::milliseconds(time_to_next_slot), + EventTypes::SlotStarted, + std::make_shared(next_slot, 0, false)); + } + + void TimelineImpl::stop() { + stopped_ = true; + } + + void TimelineImpl::on_slot_started( + std::shared_ptr msg) { + if (stopped_) [[unlikely]] { + SL_INFO(logger_, "Timeline is stopped on slot {}", msg->slot); + return; + } + + auto now = clock_->nowMsec(); + auto next_slot = (now - config_->genesis_time) / SLOT_DURATION_MS + 1; + auto time_to_next_slot = + config_->genesis_time + SLOT_DURATION_MS * next_slot - now; + + SL_INFO(logger_, "Next slot is {} in {}ms", msg->slot, time_to_next_slot); + + se_manager_->notifyDelayed( + std::chrono::milliseconds(time_to_next_slot), + EventTypes::SlotStarted, + std::make_shared(msg->slot + 1, 0, false)); + } + +} // namespace lean::app diff --git a/src/app/impl/timeline_impl.hpp b/src/app/impl/timeline_impl.hpp new file mode 100644 index 0000000..efb521e --- /dev/null +++ b/src/app/impl/timeline_impl.hpp @@ -0,0 +1,66 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include "app/timeline.hpp" +#include "se/subscription_fwd.hpp" + +namespace lean::messages { + struct SlotStarted; +} +namespace lean { + struct Config; +} +namespace lean::log { + class LoggingSystem; +} +namespace lean::clock { + class SystemClock; +} +namespace soralog { + class Logger; +} +namespace lean::app { + class Configuration; + class StateManager; +} + +namespace lean::app { + + class TimelineImpl final : public Timeline { + public: + TimelineImpl(qtils::SharedRef logsys, + qtils::SharedRef state_manager, + qtils::SharedRef se_manager, + qtils::SharedRef clock, + qtils::SharedRef config); + + void prepare(); + void start(); + void stop(); + + private: + void on_slot_started(std::shared_ptr msg); + + qtils::SharedRef logger_; + qtils::SharedRef state_manager_; + qtils::SharedRef config_; + qtils::SharedRef clock_; + qtils::SharedRef se_manager_; + + bool stopped_ = false; + + std::shared_ptr< + BaseSubscriber>> + on_slot_started_; + }; + +} // namespace lean::app diff --git a/src/app/timeline.hpp b/src/app/timeline.hpp new file mode 100644 index 0000000..81f1487 --- /dev/null +++ b/src/app/timeline.hpp @@ -0,0 +1,17 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "utils/ctor_limiters.hpp" + +namespace lean::app { + + class Timeline : NonCopyable, NonMovable { + // + }; + +} // namespace lean::app diff --git a/src/blockchain/CMakeLists.txt b/src/blockchain/CMakeLists.txt index 6f7999e..34915ab 100644 --- a/src/blockchain/CMakeLists.txt +++ b/src/blockchain/CMakeLists.txt @@ -9,6 +9,11 @@ add_library(blockchain impl/block_storage_impl.cpp impl/block_storage_error.cpp impl/block_storage_initializer.cpp + impl/block_tree_error.cpp + impl/block_tree_impl.cpp + impl/block_tree_initializer.cpp + impl/cached_tree.cpp + impl/genesis_block_header_impl.cpp ) target_link_libraries(blockchain Boost::boost diff --git a/src/blockchain/block_header_repository.hpp b/src/blockchain/block_header_repository.hpp new file mode 100644 index 0000000..1051700 --- /dev/null +++ b/src/blockchain/block_header_repository.hpp @@ -0,0 +1,54 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "types/block_header.hpp" + +namespace lean::blockchain { + + /** + * Status of a block + */ + enum class BlockStatus : uint8_t { + InChain, + Unknown, + }; + + /** + * An interface to a storage with block headers that provides several + * convenience methods, such as getting bloch number by its hash and vice + * versa or getting a block status + */ + class BlockHeaderRepository { + public: + virtual ~BlockHeaderRepository() = default; + + /** + * @return the number of the block with the provided {@param block_hash} + * in case one is in the storage or an error + */ + [[nodiscard]] virtual outcome::result getNumberByHash( + const BlockHash &block_hash) const = 0; + + /** + * @return block header with corresponding {@param block_hash} or an error + */ + [[nodiscard]] virtual outcome::result getBlockHeader( + const BlockHash &block_hash) const = 0; + + /** + * @return block header with corresponding {@param block_hash} or a none + * optional if the corresponding block header is not in storage or a + * storage error + */ + [[nodiscard]] virtual outcome::result> + tryGetBlockHeader(const BlockHash &block_hash) const = 0; + }; + +} // namespace lean::blockchain diff --git a/src/blockchain/block_storage.hpp b/src/blockchain/block_storage.hpp index b01a25f..f949519 100644 --- a/src/blockchain/block_storage.hpp +++ b/src/blockchain/block_storage.hpp @@ -8,13 +8,14 @@ #include -#include "lean_types/block.hpp" -#include "lean_types/block_body.hpp" -#include "lean_types/block_data.hpp" -#include "lean_types/block_header.hpp" -#include "lean_types/justification.hpp" -#include "lean_types/signed_block.hpp" -#include "lean_types/types.hpp" +#include "storage/buffer_map_types.hpp" +#include "types/block.hpp" +#include "types/block_body.hpp" +#include "types/block_data.hpp" +#include "types/block_header.hpp" +#include "types/justification.hpp" +#include "types/signed_block.hpp" +#include "types/types.hpp" namespace lean::blockchain { @@ -42,7 +43,7 @@ namespace lean::blockchain { /** * Get the last finalized block - * @return BlockInfo of the block + * @return BlockIndex of the block */ [[nodiscard]] virtual outcome::result getLastFinalized() const = 0; @@ -69,7 +70,47 @@ namespace lean::blockchain { * @returns vector of hashes or error */ [[nodiscard]] virtual outcome::result> getBlockHash( - BlockNumber slot) const = 0; + Slot slot) const = 0; + + class SlotIterator { + public: + static outcome::result create( + std::unique_ptr cursor) { + OUTCOME_TRY(cursor->seekLast()); + return SlotIterator(std::move(cursor)); + } + SlotIterator &operator++() { + std::ignore = cursor_->next(); + return *this; + } + SlotIterator &operator--() { + std::ignore = cursor_->prev(); + return *this; + } + [[nodiscard]] bool isValid() const { + return cursor_->isValid(); + } + [[nodiscard]] Slot slot() const { + assert(cursor_->isValid()); + return decode(cursor_->key().value()).value(); + } + [[nodiscard]] std::vector hashes() const { + assert(cursor_->isValid()); + return decode>(cursor_->value().value()).value(); + } + + private: + SlotIterator(std::unique_ptr cursor) + : cursor_(std::move(cursor)) {} + std::unique_ptr cursor_; + }; + + /** + * Gets cursor for requested or next slot-to-hash record + * @returns vector of hashes or error + */ + [[nodiscard]] virtual outcome::result seekLastSlot() + const = 0; // -- headers -- diff --git a/src/blockchain/block_storage_error.hpp b/src/blockchain/block_storage_error.hpp index bc94ab5..8920c5b 100644 --- a/src/blockchain/block_storage_error.hpp +++ b/src/blockchain/block_storage_error.hpp @@ -14,11 +14,12 @@ namespace lean::blockchain { enum class BlockStorageError : uint8_t { BLOCK_EXISTS = 1, + NO_BLOCKS_FOUND, HEADER_NOT_FOUND, GENESIS_BLOCK_ALREADY_EXISTS, GENESIS_BLOCK_NOT_FOUND, FINALIZED_BLOCK_NOT_FOUND, - BLOCK_TREE_LEAVES_NOT_FOUND + BLOCK_TREE_LEAVES_NOT_FOUND, }; } diff --git a/src/blockchain/block_tree.hpp b/src/blockchain/block_tree.hpp new file mode 100644 index 0000000..c0ae685 --- /dev/null +++ b/src/blockchain/block_tree.hpp @@ -0,0 +1,155 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "blockchain/block_header_repository.hpp" +#include "types/justification.hpp" + +namespace lean { + struct BlockBody; + struct Block; +} // namespace lean + +namespace lean::blockchain { + class BlockTree : public BlockHeaderRepository { + public: + [[nodiscard]] virtual const BlockHash &getGenesisBlockHash() const = 0; + + /** + * Checks containing of block header by provided block header + * @param hash hash of the block header we are checking + * @return containing block header or does not, or error + */ + [[nodiscard]] virtual bool has(const BlockHash &hash) const = 0; + + /** + * Get a body of the block (if present) + * @param block_hash hash of the block to get body for + * @return body, if the block exists in our storage, error in case it does + * not exist in our storage, or actual error happens + */ + [[nodiscard]] virtual outcome::result getBlockBody( + const BlockHash &block_hash) const = 0; + + /** + * Adds header to the storage + * @param header that we are adding + * @return result with success if the header's parent exists on storage and + * a new header was added. Error otherwise + */ + virtual outcome::result addBlockHeader(const BlockHeader &header) = 0; + + /** + * Adds block body to the storage + * @param block_hash that corresponds to the block which body we are adding + * @param block_body that we are adding + * @return result with success if block body was inserted. Error otherwise + */ + virtual outcome::result addBlockBody(const BlockHash &block_hash, + const BlockBody &block_body) = 0; + + /** + * Add an existent block to the tree + * @param block_hash is hash of the added block in the tree + * @param block_header is header of that block + * @return nothing or error; if error happens, no changes in the tree are + * made + */ + virtual outcome::result addExistingBlock( + const BlockHash &block_hash, const BlockHeader &block_header) = 0; + + /** + * Add a new block to the tree + * @param block to be stored and added to tree + * @return nothing or error; if error happens, no changes in the tree are + * made + * + * @note if block, which is specified in PARENT_HASH field of (\param block) + * is not in our local storage, corresponding error is returned. It is + * suggested that after getting that error, the caller would ask another + * peer for the parent block and try to insert it; this operation is to be + * repeated until a successful insertion happens + */ + virtual outcome::result addBlock(const Block &block) = 0; + + /** + * Remove leaf + * @param block_hash - hash of block to be deleted. The block must be leaf. + * @return nothing or error + */ + virtual outcome::result removeLeaf(const BlockHash &block_hash) = 0; + + /** + * Mark the block as finalized and store a finalization justification + * @param block to be finalized + * @param justification of the finalization + * @return nothing or error + */ + virtual outcome::result finalize( + const BlockHash &block, const Justification &justification) = 0; + + /** + * Get a chain of blocks from provided block to direction of the best block + * @param block from which the chain is started + * @param maximum number of blocks to be retrieved + * @return chain or blocks or error + */ + [[nodiscard]] virtual outcome::result> + getBestChainFromBlock(const BlockHash &block, uint64_t maximum) const = 0; + + /** + * Get a chain of blocks before provided block including its + * @param block to which the chain is ended + * @param maximum number of blocks to be retrieved + * @return chain or blocks or error + */ + [[nodiscard]] virtual outcome::result> + getDescendingChainToBlock(const BlockHash &block, + uint64_t maximum) const = 0; + + [[nodiscard]] virtual bool isFinalized(const BlockIndex &block) const = 0; + + /** + * Get a best leaf of the tree + * @return best leaf + * + * @note best block is also a result of "SelectBestChain": if we are the + * leader, we connect a block, which we constructed, to that best block + */ + [[nodiscard]] virtual BlockIndex bestBlock() const = 0; + + /** + * @brief Get the most recent block of the best (longest) chain among + * those that contain a block with \param target_hash + * @param target_hash is a hash of a block that the chosen chain must + * contain + */ + [[nodiscard]] virtual outcome::result getBestContaining( + const BlockHash &target_hash) const = 0; + + /** + * Get all leaves of our tree + * @return collection of the leaves + */ + [[nodiscard]] virtual std::vector getLeaves() const = 0; + + /** + * Get children of the block with specified hash + * @param block to get children of + * @return collection of children hashes or error + */ + [[nodiscard]] virtual outcome::result> getChildren( + const BlockHash &block) const = 0; + + /** + * Get the last finalized block + * @return hash of the block + */ + [[nodiscard]] virtual BlockIndex lastFinalized() const = 0; + }; + +} // namespace lean::blockchain diff --git a/src/blockchain/block_tree_error.hpp b/src/blockchain/block_tree_error.hpp new file mode 100644 index 0000000..30215b2 --- /dev/null +++ b/src/blockchain/block_tree_error.hpp @@ -0,0 +1,47 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include + +namespace lean::blockchain { + + /** + * Errors of the block tree are here, so that other modules can use them, for + * example, to compare a received error with those + */ + enum class BlockTreeError : uint8_t { + NO_PARENT = 1, + BLOCK_EXISTS, + // target block number is past the given maximum number + TARGET_IS_PAST_MAX, + // block resides on a dead fork + BLOCK_ON_DEAD_END, + // block exists in chain but not found when following all leaves backwards + EXISTING_BLOCK_NOT_FOUND, + // non-finalized block is not found + NON_FINALIZED_BLOCK_NOT_FOUND, + // justification is not found in block storage + JUSTIFICATION_NOT_FOUND, + // block body is not found in block storage + BODY_NOT_FOUND, + // block header is not found in block storage + HEADER_NOT_FOUND, + // some block in the requested chain is missing + SOME_BLOCK_IN_CHAIN_NOT_FOUND, + // block is not a leaf + BLOCK_IS_NOT_LEAF, + BLOCK_NOT_EXISTS, + BLOCK_TREE_CORRUPTED, + WRONG_WORKFLOW, + }; + +} // namespace lean::blockchain + +OUTCOME_HPP_DECLARE_ERROR(lean::blockchain, BlockTreeError) diff --git a/src/blockchain/genesis_block_header.hpp b/src/blockchain/genesis_block_header.hpp index 6f22537..c21808e 100644 --- a/src/blockchain/genesis_block_header.hpp +++ b/src/blockchain/genesis_block_header.hpp @@ -6,7 +6,7 @@ #pragma once -#include "lean_types/block_header.hpp" +#include "types/block_header.hpp" namespace lean::blockchain { diff --git a/src/blockchain/impl/block_storage_error.cpp b/src/blockchain/impl/block_storage_error.cpp index a958fcd..05508dd 100644 --- a/src/blockchain/impl/block_storage_error.cpp +++ b/src/blockchain/impl/block_storage_error.cpp @@ -11,6 +11,8 @@ OUTCOME_CPP_DEFINE_CATEGORY(lean::blockchain, BlockStorageError, e) { switch (e) { case E::BLOCK_EXISTS: return "Block already exists on the chain"; + case E::NO_BLOCKS_FOUND: + return "No blocks found"; case E::HEADER_NOT_FOUND: return "Block header was not found"; case E::GENESIS_BLOCK_ALREADY_EXISTS: diff --git a/src/blockchain/impl/block_storage_impl.cpp b/src/blockchain/impl/block_storage_impl.cpp index 96f6a99..6bdc480 100644 --- a/src/blockchain/impl/block_storage_impl.cpp +++ b/src/blockchain/impl/block_storage_impl.cpp @@ -10,9 +10,9 @@ #include "blockchain/block_storage_error.hpp" #include "blockchain/impl/storage_util.hpp" -#include "lean_types/block_data.hpp" #include "sszpp/ssz++.hpp" #include "storage/predefined_keys.hpp" +#include "types/block_data.hpp" namespace lean::blockchain { @@ -94,10 +94,10 @@ namespace lean::blockchain { } outcome::result BlockStorageImpl::assignHashToSlot( - const BlockInfo &block_index) { + const BlockIndex &block_index) { SL_DEBUG(logger_, "Add slot-to-hash for {}", block_index); auto slot_to_hash_key = slotToHashLookupKey(block_index.slot); - auto storage = storage_->getSpace(storage::Space::LookupKey); + auto storage = storage_->getSpace(storage::Space::SlotToHashes); OUTCOME_TRY(hashes, getBlockHash(block_index.slot)); if (not qtils::cxx23::ranges::contains(hashes, block_index.hash)) { hashes.emplace_back(block_index.hash); @@ -110,7 +110,7 @@ namespace lean::blockchain { const BlockIndex &block_index) { SL_DEBUG(logger_, "Remove num-to-idx for {}", block_index); auto slot_to_hash_key = slotToHashLookupKey(block_index.slot); - auto storage = storage_->getSpace(storage::Space::LookupKey); + auto storage = storage_->getSpace(storage::Space::SlotToHashes); OUTCOME_TRY(hashes, getBlockHash(block_index.slot)); auto to_erase = std::ranges::remove(hashes, block_index.hash); if (not to_erase.empty()) { @@ -126,7 +126,7 @@ namespace lean::blockchain { outcome::result> BlockStorageImpl::getBlockHash( Slot slot) const { - auto storage = storage_->getSpace(storage::Space::LookupKey); + auto storage = storage_->getSpace(storage::Space::SlotToHashes); OUTCOME_TRY(data_opt, storage->tryGet(slotToHashLookupKey(slot))); if (data_opt.has_value()) { return decode>(data_opt.value()); @@ -134,16 +134,23 @@ namespace lean::blockchain { return {{}}; } + outcome::result BlockStorageImpl::seekLastSlot() + const { + auto storage = storage_->getSpace(storage::Space::SlotToHashes); + return SlotIterator::create(storage->cursor()); + } + // outcome::result> // BlockStorageImpl::getBlockHash(const BlockId &block_id) const // { // return visit_in_place( // block_id, - // [&](const BlockNumber &block_number) + // [&](const Slot &slot) // -> outcome::result> { - // auto key_space = storage_->getSpace(storage::Space::kLookupKey); + // auto key_space = + // storage_->getSpace(storage::Space::SlotToHashes); // OUTCOME_TRY(data_opt, - // key_space->tryGet(slotToHashLookupKey(block_number))); + // key_space->tryGet(slotToHashLookupKey(slot))); // if (data_opt.has_value()) { // OUTCOME_TRY(block_hash, // BlockHash::fromSpan(data_opt.value())); @@ -245,22 +252,20 @@ namespace lean::blockchain { outcome::result BlockStorageImpl::putBlock( const BlockData &block) { // insert provided block's parts into the database - OUTCOME_TRY(block_hash, putBlockHeader(block.header.value())); - - OUTCOME_TRY(encoded_header, encode(block.header)); - OUTCOME_TRY(putToSpace(*storage_, - storage::Space::Header, - block_hash, - std::move(encoded_header))); - - OUTCOME_TRY(encoded_body, encode(block.body)); - OUTCOME_TRY(putToSpace( - *storage_, storage::Space::Body, block_hash, std::move(encoded_body))); + OUTCOME_TRY(block_hash, putBlockHeader(*block.header)); + + if (block.body.has_value()) { + OUTCOME_TRY(encoded_body, encode(*block.body)); + OUTCOME_TRY(putToSpace(*storage_, + storage::Space::Body, + block_hash, + std::move(encoded_body))); + } logger_->info("Added block {} as child of {}", BlockIndex{block.header->slot, block_hash}, block.header->parent_root); - return BlockHash{}; // block_hash; + return block_hash; } outcome::result> BlockStorageImpl::getBlock( @@ -300,7 +305,7 @@ namespace lean::blockchain { { // Remove slot-to-hash assigning auto num_to_hash_key = slotToHashLookupKey(block_index.slot); - auto key_space = storage_->getSpace(storage::Space::LookupKey); + auto key_space = storage_->getSpace(storage::Space::SlotToHashes); OUTCOME_TRY(hash_opt, key_space->tryGet(num_to_hash_key.view())); if (hash_opt == block_hash) { if (auto res = key_space->remove(num_to_hash_key); res.has_error()) { diff --git a/src/blockchain/impl/block_storage_impl.hpp b/src/blockchain/impl/block_storage_impl.hpp index ea0f0d4..530621f 100644 --- a/src/blockchain/impl/block_storage_impl.hpp +++ b/src/blockchain/impl/block_storage_impl.hpp @@ -39,7 +39,9 @@ namespace lean::blockchain { const BlockIndex &block_index) override; outcome::result> getBlockHash( - BlockNumber slot) const override; + Slot slot) const override; + + outcome::result seekLastSlot() const override; // -- header -- diff --git a/src/blockchain/impl/block_storage_initializer.cpp b/src/blockchain/impl/block_storage_initializer.cpp index 78fa7e9..182ad3f 100644 --- a/src/blockchain/impl/block_storage_initializer.cpp +++ b/src/blockchain/impl/block_storage_initializer.cpp @@ -15,7 +15,7 @@ #include "blockchain/genesis_block_header.hpp" #include "blockchain/impl/block_storage_impl.hpp" #include "blockchain/impl/storage_util.hpp" -#include "lean_types/block.hpp" +#include "types/block.hpp" namespace lean::blockchain { @@ -43,9 +43,8 @@ namespace lean::blockchain { if (not genesis_header_is_exist) { // genesis block initialization - BlockData genesis_block{ - .header = *genesis_header, - }; + BlockData genesis_block; + genesis_block.header.emplace(*genesis_header); auto res = block_storage.putBlock(genesis_block); if (res.has_error()) { diff --git a/src/blockchain/impl/block_storage_initializer.hpp b/src/blockchain/impl/block_storage_initializer.hpp index 688d07f..923b3b5 100644 --- a/src/blockchain/impl/block_storage_initializer.hpp +++ b/src/blockchain/impl/block_storage_initializer.hpp @@ -6,8 +6,8 @@ #pragma once -#include #include +#include #include namespace lean::log { diff --git a/src/blockchain/impl/block_tree_error.cpp b/src/blockchain/impl/block_tree_error.cpp new file mode 100644 index 0000000..c3f18b3 --- /dev/null +++ b/src/blockchain/impl/block_tree_error.cpp @@ -0,0 +1,45 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "blockchain/block_tree_error.hpp" + +OUTCOME_CPP_DEFINE_CATEGORY(lean::blockchain, BlockTreeError, e) { + using E = lean::blockchain::BlockTreeError; + switch (e) { + case E::NO_PARENT: + return "block, which should have been added, has no known parent"; + case E::BLOCK_EXISTS: + return "block, which should have been inserted, already exists in the " + "tree"; + case E::SOME_BLOCK_IN_CHAIN_NOT_FOUND: + return "one of the blocks for getting the chain was not found in the " + "local storage"; + case E::TARGET_IS_PAST_MAX: + return "target block number is past the given maximum number"; + case E::BLOCK_ON_DEAD_END: + return "block resides on a dead fork"; + case E::EXISTING_BLOCK_NOT_FOUND: + return "block exists in chain but not found when following all leaves " + "backwards"; + case E::NON_FINALIZED_BLOCK_NOT_FOUND: + return "a non-finalized block is not found"; + case E::JUSTIFICATION_NOT_FOUND: + return "the requested justification is not found in block storage"; + case E::HEADER_NOT_FOUND: + return "the requested block header is not found in block storage"; + case E::BODY_NOT_FOUND: + return "the requested block body is not found in block storage"; + case E::BLOCK_IS_NOT_LEAF: + return "the target block is not a leaf"; + case E::BLOCK_NOT_EXISTS: + return "target block doesn't exist"; + case E::BLOCK_TREE_CORRUPTED: + return "block tree corrupted"; + case E::WRONG_WORKFLOW: + return "trying to use block tree initializing data second time"; + } + return "unknown error"; +} diff --git a/src/blockchain/impl/block_tree_impl.cpp b/src/blockchain/impl/block_tree_impl.cpp new file mode 100644 index 0000000..f8d55bd --- /dev/null +++ b/src/blockchain/impl/block_tree_impl.cpp @@ -0,0 +1,853 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "blockchain/impl/block_tree_impl.hpp" + +#include +#include + +#include + +#include "blockchain/block_tree_error.hpp" +#include "blockchain/impl/block_tree_initializer.hpp" +#include "blockchain/impl/cached_tree.hpp" +#include "metrics/histogram_timer.hpp" +#include "modules/shared/prodution_types.tmp.hpp" +#include "se/subscription.hpp" +#include "se/subscription_fwd.hpp" +#include "tests/testutil/literals.hpp" +#include "types/block.hpp" +#include "types/block_header.hpp" + +namespace lean::blockchain { + BlockTreeImpl::SafeBlockTreeData::SafeBlockTreeData(BlockTreeData data) + : block_tree_data_{std::move(data)} {} + + BlockTreeImpl::BlockTreeImpl( + qtils::SharedRef logsys, + qtils::SharedRef storage, + qtils::SharedRef hasher, + std::shared_ptr se_manager, + qtils::SharedRef initializer) + : log_(logsys->getLogger("BlockTree", "block_tree")), + se_manager_(std::move(se_manager)), + block_tree_data_{{ + .storage_ = std::move(storage), + .tree_ = std::make_unique( + std::get<0>(initializer->nonFinalizedSubTree())), + .hasher_ = std::move(hasher), + }}, + metric_best_block_height_("block_chain_height", + "Block height info of the chain", + {{"status", "best"}}), + metric_finalized_block_height_("block_chain_height", + "Block height info of the chain", + {{"status", "finalized"}}), + metric_known_chain_leaves_("number_leaves", + "Number of known chain leaves (aka forks)") { + block_tree_data_.sharedAccess([&](const BlockTreeData &p) { + metric_best_block_height_->set(bestBlockNoLock(p).slot); + metric_finalized_block_height_->set(getLastFinalizedNoLock(p).slot); + metric_known_chain_leaves_->set(p.tree_->leafCount()); + // telemetry_->setGenesisBlockHash(getGenesisBlockHash()); + }); + + // Add non-finalized block to the block tree + for (const auto &[block, header] : + std::get<1>(initializer->nonFinalizedSubTree())) { + auto res = BlockTreeImpl::addExistingBlock(block.hash, header); + if (res.has_error()) { + SL_WARN( + log_, "Failed to add existing block {}: {}", block, res.error()); + } + SL_TRACE(log_, + "Existing non-finalized block {} is added to block tree", + block); + } + } + + const BlockHash &BlockTreeImpl::getGenesisBlockHash() const { + return block_tree_data_ + .sharedAccess([&](const BlockTreeData &p) + -> std::reference_wrapper { + if (p.genesis_block_hash_.has_value()) { + return p.genesis_block_hash_.value(); + } + + auto res = p.storage_->getBlockHash(0); + BOOST_ASSERT_MSG(res.has_value() and res.value().size() == 1, + "Block tree must contain exactly one genesis block"); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + const_cast &>(p.genesis_block_hash_) + .emplace(res.value()[0]); + return p.genesis_block_hash_.value(); + }) + .get(); + } + + outcome::result BlockTreeImpl::addBlockHeader( + const BlockHeader &header) { + return block_tree_data_.exclusiveAccess( + [&](BlockTreeData &p) -> outcome::result { + auto parent_opt = p.tree_->find(header.parent_root); + if (not parent_opt.has_value()) { + return BlockTreeError::NO_PARENT; + } + const auto &parent = parent_opt.value(); + OUTCOME_TRY(p.storage_->putBlockHeader(header)); + + // update local meta with the new block + auto new_node = std::make_shared(header.index(), parent); + + auto reorg = p.tree_->add(new_node); + OUTCOME_TRY(reorgAndPrune(p, {std::move(reorg), {}})); + + auto header_ptr = std::make_shared(header); + se_manager_->notify(EventTypes::BlockAdded, header_ptr); + + SL_VERBOSE( + log_, "Block {} has been added into block tree", header.index()); + + return outcome::success(); + }); + } + + outcome::result BlockTreeImpl::addBlock(const Block &block) { + return block_tree_data_.exclusiveAccess( + [&](BlockTreeData &p) -> outcome::result { + // Check if we know parent of this block; if not, we cannot insert it + auto parent_opt = p.tree_->find(block.parent_root); + if (not parent_opt.has_value()) { + return BlockTreeError::NO_PARENT; + } + const auto &parent = parent_opt.value(); + + BlockHeader header; + header.slot = block.slot; + header.proposer_index = block.proposer_index; + header.parent_root = block.parent_root; + header.state_root = block.state_root; + header.body_root = {}; // ssz::hash_tree_root(block.body); + header.updateHash(*p.hasher_); + + SL_DEBUG(log_, "Adding block {}", header.index()); + + BlockData block_data; + block_data.hash = header.hash(); + block_data.header.emplace(header); + block_data.body.emplace(block.body); + block_data.signature = {}; + + // Save block + OUTCOME_TRY(block_hash, p.storage_->putBlock(block_data)); + BOOST_ASSERT(block_hash == header.hash()); + + // Update local meta with the block + auto new_node = std::make_shared(header.index(), parent); + + auto reorg = p.tree_->add(new_node); + OUTCOME_TRY(reorgAndPrune(p, {std::move(reorg), {}})); + + auto msg = std::make_shared( + header, header.index() == bestBlock()); + se_manager_->notify(EventTypes::BlockAdded, msg); + + SL_VERBOSE( + log_, "Block {} has been added into block tree", header.index()); + return outcome::success(); + }); + } + + void BlockTreeImpl::notifyChainEventsEngine(EventTypes event, + const BlockHeader &header) { + BOOST_ASSERT(header.hash_opt.has_value()); + auto header_ptr = std::make_shared(header); + se_manager_->notify(event, header_ptr); + } + + outcome::result BlockTreeImpl::removeLeaf(const BlockHash &block_hash) { + return block_tree_data_.exclusiveAccess( + [&](BlockTreeData &p) -> outcome::result { + auto finalized = getLastFinalizedNoLock(p); + if (block_hash == finalized.hash) { + OUTCOME_TRY(header, getBlockHeader(block_hash)); + // OUTCOME_TRY(p.storage_->removeJustification(finalized.hash)); + + OUTCOME_TRY(slot, getNumberByHash(header.parent_root)); + auto parent = BlockIndex(slot, header.parent_root); + + ReorgAndPrune changes{ + .reorg = Reorg{.common = parent, .revert = {finalized}}, + .prune = {finalized}, + }; + p.tree_ = std::make_unique(parent); + OUTCOME_TRY(reorgAndPrune(p, changes)); + return outcome::success(); + } + if (not p.tree_->isLeaf(block_hash)) { + return BlockTreeError::BLOCK_IS_NOT_LEAF; + } + auto changes = p.tree_->removeLeaf(block_hash); + OUTCOME_TRY(reorgAndPrune(p, changes)); + return outcome::success(); + }); + } + + // outcome::result BlockTreeImpl::markAsParachainDataBlock( + // const BlockHash &block_hash) { + // return block_tree_data_.exclusiveAccess( + // [&](BlockTreeData &p) -> outcome::result { + // SL_TRACE(log_, "Trying to adjust weight for block {}", block_hash); + // + // auto node = p.tree_->find(block_hash); + // if (node == nullptr) { + // SL_WARN(log_, "Block {} doesn't exists in block tree", + // block_hash); return BlockTreeError::BLOCK_NOT_EXISTS; + // } + // + // node->contains_approved_para_block = true; + // return outcome::success(); + // }); + // } + + // outcome::result BlockTreeImpl::markAsRevertedBlocks( + // const std::vector &block_hashes) { + // return block_tree_data_.exclusiveAccess( + // [&](BlockTreeData &p) -> outcome::result { + // bool need_to_refresh_best = false; + // auto best = bestBlockNoLock(p); + // for (const auto &block_hash : block_hashes) { + // auto node_opt = p.tree_->find(block_hash); + // if (not node_opt.has_value()) { + // SL_WARN( + // log_, "Block {} doesn't exists in block tree", block_hash); + // continue; + // } + // auto &node = node_opt.value(); + // + // if (not node->reverted) { + // std::queue> to_revert; + // to_revert.push(std::move(node)); + // while (not to_revert.empty()) { + // auto &reverting_tree_node = to_revert.front(); + // + // reverting_tree_node->reverted = true; + // + // if (reverting_tree_node->info == best) { + // need_to_refresh_best = true; + // } + // + // for (auto &child : reverting_tree_node->children) { + // if (not child->reverted) { + // to_revert.push(child); + // } + // } + // + // to_revert.pop(); + // } + // } + // } + // if (need_to_refresh_best) { + // p.tree_->forceRefreshBest(); + // } + // return outcome::success(); + // }); + // } + + outcome::result BlockTreeImpl::addExistingBlockNoLock( + BlockTreeData &p, + const BlockHash &block_hash, + const BlockHeader &block_header) { + SL_TRACE(log_, + "Trying to add block {} into block tree", + BlockIndex(block_header.slot, block_hash)); + + auto node_opt = p.tree_->find(block_hash); + // Check if the tree doesn't have this block; if not, we skip that + if (node_opt.has_value()) { + SL_TRACE(log_, + "Block {} exists in block tree", + BlockIndex(block_header.slot, block_hash)); + return BlockTreeError::BLOCK_EXISTS; + } + + auto parent_opt = p.tree_->find(block_header.parent_root); + + // Check if we know the parent of this block; if not, we cannot insert it + if (not parent_opt.has_value()) { + SL_TRACE(log_, + "Block {} parent of {} has not found in block tree. " + "Trying to restore missed branch", + block_header.parent_root, + BlockIndex(block_header.slot, block_hash)); + + // Trying to restore a missed branch + std::stack> to_add; + + auto finalized = getLastFinalizedNoLock(p).slot; + + for (auto hash = block_header.parent_root;;) { + OUTCOME_TRY(header, p.storage_->getBlockHeader(hash)); + BlockIndex block_index(header.slot, hash); + SL_TRACE(log_, + "Block {} has found in storage and enqueued to add", + block_index); + + if (header.slot <= finalized) { + return BlockTreeError::BLOCK_ON_DEAD_END; + } + + auto parent_hash = header.parent_root; + to_add.emplace(hash, header); + + if (p.tree_->find(parent_hash).has_value()) { + SL_TRACE(log_, + "Block {} parent of {} has found in block tree", + parent_hash, + block_index); + break; + } + + SL_TRACE(log_, + "Block {} has not found in block tree. " + "Trying to restore from storage", + parent_hash); + + hash = parent_hash; + } + + while (not to_add.empty()) { + const auto &[hash, header] = to_add.top(); + OUTCOME_TRY(addExistingBlockNoLock(p, hash, header)); + to_add.pop(); + } + + parent_opt = p.tree_->find(block_header.parent_root); + BOOST_ASSERT_MSG(parent_opt.has_value(), + "Parent must be restored at this moment"); + + SL_TRACE(log_, + "Trying to add block {} into block tree", + BlockIndex(block_header.slot, block_hash)); + } + auto &parent = parent_opt.value(); + + // Update local meta with the block + auto new_node = std::make_shared(block_header.index(), parent); + + auto reorg = p.tree_->add(new_node); + OUTCOME_TRY(reorgAndPrune(p, {std::move(reorg), {}})); + + SL_VERBOSE(log_, + "Block {} has been restored in block tree from storage", + block_header.index()); + + return outcome::success(); + } + + outcome::result BlockTreeImpl::addExistingBlock( + const BlockHash &block_hash, const BlockHeader &block_header) { + return block_tree_data_.exclusiveAccess( + [&](BlockTreeData &p) -> outcome::result { + return addExistingBlockNoLock(p, block_hash, block_header); + }); + } + + outcome::result BlockTreeImpl::addBlockBody(const BlockHash &block_hash, + const BlockBody &body) { + return block_tree_data_.exclusiveAccess( + [&](const BlockTreeData &p) -> outcome::result { + return p.storage_->putBlockBody(block_hash, body); + }); + } + + outcome::result BlockTreeImpl::finalize( + const BlockHash &block_hash, const Justification &justification) { + return block_tree_data_.exclusiveAccess([&](BlockTreeData &p) + -> outcome::result { + auto last_finalized_block_info = getLastFinalizedNoLock(p); + if (block_hash == last_finalized_block_info.hash) { + return outcome::success(); + } + const auto node_opt = p.tree_->find(block_hash); + if (node_opt.has_value()) { + auto &node = node_opt.value(); + + SL_DEBUG(log_, "Finalizing block {}", node->index); + + OUTCOME_TRY(header, p.storage_->getBlockHeader(block_hash)); + + OUTCOME_TRY(p.storage_->putJustification(justification, block_hash)); + + std::vector retired_hashes; + for (auto parent = node->parent(); parent; parent = parent->parent()) { + retired_hashes.emplace_back(parent->index); + } + + auto changes = p.tree_->finalize(node); + OUTCOME_TRY(reorgAndPrune(p, changes)); + + auto msg = std::make_shared( + header.index(), std::move(retired_hashes)); + se_manager_->notify(EventTypes::BlockFinalized, msg); + + + log_->info("Finalized block {}", node->index); + metric_finalized_block_height_->set(node->index.slot); + + } else { + OUTCOME_TRY(header, p.storage_->getBlockHeader(block_hash)); + const auto header_number = header.slot; + if (header_number >= last_finalized_block_info.slot) { + return BlockTreeError::NON_FINALIZED_BLOCK_NOT_FOUND; + } + + OUTCOME_TRY(hashes, p.storage_->getBlockHash(header_number)); + + if (not qtils::cxx23::ranges::contains(hashes, block_hash)) { + return BlockTreeError::BLOCK_ON_DEAD_END; + } + + // if (not p.justification_storage_policy_ + // ->shouldStoreFor(header, last_finalized_block_info.slot) + // .value()) { + // return outcome::success(); + // } + OUTCOME_TRY(justification_opt, + p.storage_->getJustification(block_hash)); + if (justification_opt.has_value()) { + // block already has justification (in DB), fine + return outcome::success(); + } + OUTCOME_TRY(p.storage_->putJustification(justification, block_hash)); + } + return outcome::success(); + }); + } + + bool BlockTreeImpl::has(const BlockHash &hash) const { + return block_tree_data_.sharedAccess([&](const BlockTreeData &p) { + return p.tree_->find(hash) or p.storage_->hasBlockHeader(hash).value(); + }); + } + + outcome::result BlockTreeImpl::getBlockHeaderNoLock( + const BlockTreeData &p, const BlockHash &block_hash) const { + return p.storage_->getBlockHeader(block_hash); + } + + outcome::result BlockTreeImpl::getBlockHeader( + const BlockHash &block_hash) const { + return block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) -> outcome::result { + return getBlockHeaderNoLock(p, block_hash); + }); + } + + outcome::result> BlockTreeImpl::tryGetBlockHeader( + const BlockHash &block_hash) const { + return block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) + -> outcome::result> { + auto header = p.storage_->getBlockHeader(block_hash); + if (header) { + return header.value(); + } + const auto &header_error = header.error(); + if (header_error == BlockTreeError::HEADER_NOT_FOUND) { + return std::nullopt; + } + return header_error; + }); + } + + outcome::result BlockTreeImpl::getBlockBody( + const BlockHash &block_hash) const { + return block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) -> outcome::result { + OUTCOME_TRY(body, p.storage_->getBlockBody(block_hash)); + if (body.has_value()) { + return body.value(); + } + return BlockTreeError::BODY_NOT_FOUND; + }); + } + + // outcome::result + // BlockTreeImpl::getBlockJustification( + // const BlockHash &block_hash) const { + // return block_tree_data_.sharedAccess( + // [&](const BlockTreeData &p) + // -> outcome::result { + // OUTCOME_TRY(justification, + // p.storage_->getJustification(block_hash)); if + // (justification.has_value()) { + // return justification.value(); + // } + // return BlockTreeError::JUSTIFICATION_NOT_FOUND; + // }); + // } + + outcome::result> BlockTreeImpl::getBestChainFromBlock( + const BlockHash &block, uint64_t maximum) const { + return block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) -> outcome::result> { + auto block_header_res = p.storage_->getBlockHeader(block); + if (block_header_res.has_error()) { + log_->error("cannot retrieve block {}: {}", + block, + block_header_res.error()); + return BlockTreeError::HEADER_NOT_FOUND; + } + auto start_block_number = block_header_res.value().slot; + + if (maximum == 1) { + return std::vector{block}; + } + + auto current_depth = bestBlockNoLock(p).slot; + + if (start_block_number >= current_depth) { + return std::vector{block}; + } + + auto count = std::min( + current_depth - start_block_number + 1, maximum); + + Slot finish_block_number = start_block_number + count - 1; + + auto finish_block_hash_res = + p.storage_->getBlockHash(finish_block_number); + if (finish_block_hash_res.has_error()) { + log_->error("cannot retrieve block with number {}: {}", + finish_block_number, + finish_block_hash_res.error()); + return BlockTreeError::HEADER_NOT_FOUND; + } + const auto &finish_block_hash = finish_block_hash_res.value()[0]; + + OUTCOME_TRY( + chain, + getDescendingChainToBlockNoLock(p, finish_block_hash, count)); + + if (chain.back() != block) { + return std::vector{block}; + } + std::ranges::reverse(chain); + return chain; + }); + } + + outcome::result> + BlockTreeImpl::getDescendingChainToBlockNoLock(const BlockTreeData &p, + const BlockHash &to_block, + uint64_t maximum) const { + std::vector chain; + + auto hash = to_block; + + // Try to retrieve from cached tree + if (auto node_opt = p.tree_->find(hash); node_opt.has_value()) { + auto node = node_opt.value(); + while (maximum > chain.size()) { + auto parent = node->parent(); + if (not parent) { + hash = node->index.hash; + break; + } + chain.emplace_back(node->index.hash); + node = parent; + } + } + + while (maximum > chain.size()) { + auto header_res = p.storage_->getBlockHeader(hash); + if (header_res.has_error()) { + if (chain.empty()) { + log_->error("Cannot retrieve block with hash {}: {}", + hash, + header_res.error()); + return header_res.error(); + } + break; + } + const auto &header = header_res.value(); + chain.emplace_back(hash); + + if (header.slot == 0) { + break; + } + hash = header.parent_root; + } + return chain; + } + + outcome::result> + BlockTreeImpl::getDescendingChainToBlock(const BlockHash &block, + uint64_t maximum) const { + return block_tree_data_.sharedAccess([&](const BlockTreeData &p) { + return getDescendingChainToBlockNoLock(p, block, maximum); + }); + } + + // outcome::result> BlockTreeImpl::getChainByBlocks( + // const BlockHash &ancestor, const BlockHash &descendant) const { + // return block_tree_data_.sharedAccess( + // [&](const BlockTreeData &p) + // -> outcome::result> { + // OUTCOME_TRY(from_header, p.storage_->getBlockHeader(ancestor)); + // auto from = from_header.slot; + // OUTCOME_TRY(to_header, p.storage_->getBlockHeader(descendant)); + // auto to = to_header.slot; + // if (to < from) { + // return BlockTreeError::TARGET_IS_PAST_MAX; + // } + // auto count = to - from + 1; + // OUTCOME_TRY(chain, + // getDescendingChainToBlockNoLock(p, descendant, count)); + // if (chain.size() != count) { + // return BlockTreeError::EXISTING_BLOCK_NOT_FOUND; + // } + // if (chain.back() != ancestor) { + // return BlockTreeError::BLOCK_ON_DEAD_END; + // } + // std::ranges::reverse(chain); + // return chain; + // }); + // } + + // bool BlockTreeImpl::hasDirectChainNoLock( + // const BlockTreeData &p, + // const BlockHash &ancestor, + // const BlockHash &descendant) const { + // if (ancestor == descendant) { + // return true; + // } + // auto ancestor_node_ptr = p.tree_->find(ancestor); + // auto descendant_node_ptr = p.tree_->find(descendant); + // if (ancestor_node_ptr and descendant_node_ptr) { + // return canDescend(*descendant_node_ptr, *ancestor_node_ptr); + // } + // + // /* + // * check that ancestor is above descendant + // * optimization that prevents reading blockDB up the genesis + // * TODO (xDimon) it could be not right place for this check + // * or changing logic may make it obsolete + // * block numbers may be obtained somewhere else + // */ + // Slot ancestor_depth = 0u; + // Slot descendant_depth = 0u; + // if (ancestor_node_ptr) { + // ancestor_depth = (*ancestor_node_ptr)->index.slot; + // } else { + // auto header_res = p.storage_->getBlockHeader(ancestor); + // if (!header_res) { + // return false; + // } + // ancestor_depth = header_res.value().slot; + // } + // if (descendant_node_ptr) { + // descendant_depth = (*descendant_node_ptr)->index.slot; + // } else { + // auto header_res = p.storage_->getBlockHeader(descendant); + // if (!header_res) { + // return false; + // } + // descendant_depth = header_res.value().slot; + // } + // if (descendant_depth < ancestor_depth) { + // SL_DEBUG(log_, + // "Ancestor block is lower. {} in comparison with {}", + // BlockIndex(ancestor_depth, ancestor), + // BlockIndex(descendant_depth, descendant)); + // return false; + // } + // + // // Try to use optimal way, if ancestor and descendant in the finalized + // // chain + // auto finalized = [&](const BlockHash &hash, + // Slot number) { + // return number <= getLastFinalizedNoLock(p).slot + // and p.storage_->getBlockHash(number) + // == outcome::success( + // std::optional(hash)); + // }; + // if (descendant_node_ptr or finalized(descendant, descendant_depth)) { + // return finalized(ancestor, ancestor_depth); + // } + // + // auto current_hash = descendant; + // while (current_hash != ancestor) { + // auto current_header_res = p.storage_->getBlockHeader(current_hash); + // if (!current_header_res) { + // return false; + // } + // if (current_header_res.value().slot <= ancestor_depth) { + // return false; + // } + // current_hash = current_header_res.value().parent_root; + // } + // return true; + // } + // + // bool BlockTreeImpl::hasDirectChain( + // const BlockHash &ancestor, + // const BlockHash &descendant) const { + // return block_tree_data_.sharedAccess([&](const BlockTreeData &p) { + // return hasDirectChainNoLock(p, ancestor, descendant); + // }); + // } + + bool BlockTreeImpl::isFinalized(const BlockIndex &block) const { + return block_tree_data_.sharedAccess([&](const BlockTreeData &p) { + if (block.slot > getLastFinalizedNoLock(p).slot) { + return false; + } + auto res = p.storage_->getBlockHash(block.slot); + return res.has_value() and not res.value().empty() + and res.value().front() == block.hash; + }); + } + + BlockIndex BlockTreeImpl::bestBlockNoLock(const BlockTreeData &p) const { + return p.tree_->best(); + } + + BlockIndex BlockTreeImpl::bestBlock() const { + return block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) { return bestBlockNoLock(p); }); + } + + outcome::result BlockTreeImpl::getBestContaining( + const BlockHash &target_hash) const { + return block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) -> outcome::result { + if (getLastFinalizedNoLock(p).hash == target_hash) { + return bestBlockNoLock(p); + } + + auto target_node_opt = p.tree_->find(target_hash); + + // If a target has not found in the block tree (in memory), + // it means block finalized or discarded + if (not target_node_opt.has_value()) { + OUTCOME_TRY(target_header, p.storage_->getBlockHeader(target_hash)); + auto target_number = target_header.slot; + + OUTCOME_TRY(hashes, p.storage_->getBlockHash(target_number)); + + if (not qtils::cxx23::ranges::contains(hashes, target_hash)) { + return BlockTreeError::BLOCK_ON_DEAD_END; + } + + return bestBlockNoLock(p); + } + + return p.tree_->bestWith(target_node_opt.value()); + }); + } + + std::vector BlockTreeImpl::getLeavesNoLock( + const BlockTreeData &p) const { + return p.tree_->leafHashes(); + } + + std::vector BlockTreeImpl::getLeaves() const { + return block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) { return getLeavesNoLock(p); }); + } + + // std::vector BlockTreeImpl::getLeavesInfo() const { + // return block_tree_data_.sharedAccess( + // [&](const BlockTreeData &p) { return p.tree_->leafInfo(); }); + // } + + outcome::result> BlockTreeImpl::getChildren( + const BlockHash &block) const { + return block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) -> outcome::result> { + if (auto node_opt = p.tree_->find(block); node_opt.has_value()) { + const auto &node = node_opt.value(); + std::vector result; + result.reserve(node->children.size()); + for (const auto &child : node->children) { + result.push_back(child->index.hash); + } + return result; + } + OUTCOME_TRY(header, p.storage_->getBlockHeader(block)); + + // TODO slot of children may be greater then parent's by more then one + return p.storage_->getBlockHash(header.slot + 1); + }); + } + + BlockIndex BlockTreeImpl::getLastFinalizedNoLock( + const BlockTreeData &p) const { + return p.tree_->finalized(); + } + + BlockIndex BlockTreeImpl::lastFinalized() const { + return block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) { return getLastFinalizedNoLock(p); }); + } + + outcome::result BlockTreeImpl::reorgAndPrune( + const BlockTreeData &p, const ReorgAndPrune &changes) { + OUTCOME_TRY(p.storage_->setBlockTreeLeaves(p.tree_->leafHashes())); + metric_known_chain_leaves_->set(p.tree_->leafCount()); + if (changes.reorg) { + for (auto &block : changes.reorg->revert) { + OUTCOME_TRY(p.storage_->deassignHashToSlot(block)); + } + for (auto &block : changes.reorg->apply) { + OUTCOME_TRY(p.storage_->assignHashToSlot(block)); + } + if (not changes.reorg->apply.empty()) { + metric_best_block_height_->set(changes.reorg->apply.back().slot); + } else { + metric_best_block_height_->set(changes.reorg->common.slot); + } + } + + // remove from storage + for (const auto &[_, hash] : changes.prune) { + OUTCOME_TRY(p.storage_->removeBlock(hash)); + } + + return outcome::success(); + } + + // BlockHeaderRepository methods + + outcome::result BlockTreeImpl::getNumberByHash( + const BlockHash &block_hash) const { + auto slot_opt = block_tree_data_.sharedAccess( + [&](const BlockTreeData &p) -> std::optional { + if (auto node = p.tree_->find(block_hash)) { + return node.value()->index.slot; + } + return std::nullopt; + }); + if (slot_opt.has_value()) { + return slot_opt.value(); + } + OUTCOME_TRY(header, getBlockHeader(block_hash)); + return header.slot; + } + + // outcome::result BlockTreeImpl::getHashByNumber( + // Slot number) const { + // OUTCOME_TRY(block_hash_opt, getBlockHash(number)); + // if (block_hash_opt.has_value()) { + // return block_hash_opt.value(); + // } + // return BlockTreeError::HEADER_NOT_FOUND; + // } + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/block_tree_impl.hpp b/src/blockchain/impl/block_tree_impl.hpp new file mode 100644 index 0000000..955f0e5 --- /dev/null +++ b/src/blockchain/impl/block_tree_impl.hpp @@ -0,0 +1,206 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include + +#include "blockchain/block_storage.hpp" +#include "blockchain/block_tree.hpp" +#include "blockchain/impl/block_tree_initializer.hpp" +#include "blockchain/impl/cached_tree.hpp" +#include "log/logger.hpp" +#include "metrics/histogram_timer.hpp" +#include "se/impl/common.hpp" +#include "se/subscription.hpp" +#include "se/subscription_fwd.hpp" + +namespace lean::metrics { + class Registry; +} +namespace lean::app { + class Configuration; +} + +namespace lean::blockchain { + class BlockTreeInitializer; +} + +namespace lean::blockchain { + + class BlockTreeImpl final + : public BlockTree, + public std::enable_shared_from_this { + public: + BlockTreeImpl(qtils::SharedRef logsys, + qtils::SharedRef storage, + qtils::SharedRef hasher, + std::shared_ptr se_manager, + qtils::SharedRef initializer); + + ~BlockTreeImpl() override = default; + + const BlockHash &getGenesisBlockHash() const override; + + bool has(const BlockHash &hash) const override; + + outcome::result getBlockHeader( + const BlockHash &block_hash) const override; + + outcome::result> tryGetBlockHeader( + const BlockHash &block_hash) const override; + + outcome::result getBlockBody( + const BlockHash &block_hash) const override; + + // outcome::result getBlockJustification( + // const BlockHash &block_hash) const override; + + outcome::result addBlockHeader(const BlockHeader &header) override; + + outcome::result addBlock(const Block &block) override; + + outcome::result removeLeaf(const BlockHash &block_hash) override; + + outcome::result addExistingBlock( + const BlockHash &block_hash, const BlockHeader &block_header) override; + + outcome::result addBlockBody(const BlockHash &block_hash, + const BlockBody &body) override; + + outcome::result finalize(const BlockHash &block_hash, + const Justification &justification) override; + + outcome::result> getBestChainFromBlock( + const BlockHash &block, uint64_t maximum) const override; + + outcome::result> getDescendingChainToBlock( + const BlockHash &block, uint64_t maximum) const override; + + // outcome::result> getChainByBlocks( + // const BlockHash &ancestor, + // const BlockHash &descendant) const override; + // + // bool hasDirectChain(const BlockHash &ancestor, + // const BlockHash &descendant) const override; + + bool isFinalized(const BlockIndex &block) const override; + + BlockIndex bestBlock() const override; + + outcome::result getBestContaining( + const BlockHash &target_hash) const override; + + std::vector getLeaves() const override; + // std::vector getLeavesInfo() const override; + + outcome::result> getChildren( + const BlockHash &block) const override; + + BlockIndex lastFinalized() const override; + + // BlockHeaderRepository methods + + outcome::result getNumberByHash( + const BlockHash &block_hash) const override; + + // outcome::result getHashByNumber( + // Slot slot) const override; + + private: + struct BlockTreeData { + qtils::SharedRef storage_; + // std::shared_ptr state_pruner_; + std::unique_ptr tree_; + qtils::SharedRef hasher_; + // std::shared_ptr + // justification_storage_policy_; + std::optional genesis_block_hash_; + // BlocksPruning blocks_pruning_; + }; + + outcome::result reorgAndPrune(const BlockTreeData &p, + const ReorgAndPrune &changes); + + outcome::result getBlockHeaderNoLock( + const BlockTreeData &p, const BlockHash &block_hash) const; + + // outcome::result pruneTrie(const BlockTreeData &block_tree_data, + // Slot new_finalized); + + BlockIndex getLastFinalizedNoLock(const BlockTreeData &p) const; + BlockIndex bestBlockNoLock(const BlockTreeData &p) const; + + // bool hasDirectChainNoLock(const BlockTreeData &p, + // const BlockHash &ancestor, + // const BlockHash &descendant); + std::vector getLeavesNoLock(const BlockTreeData &p) const; + + outcome::result> getDescendingChainToBlockNoLock( + const BlockTreeData &p, + const BlockHash &to_block, + uint64_t maximum) const; + + outcome::result addExistingBlockNoLock( + BlockTreeData &p, + const BlockHash &block_hash, + const BlockHeader &block_header); + + void notifyChainEventsEngine(EventTypes event, const BlockHeader &header); + + class SafeBlockTreeData { + public: + explicit SafeBlockTreeData(BlockTreeData data); + + template + decltype(auto) exclusiveAccess(F &&f) { + // if this thread owns the mutex, it shall + // not be unlocked until this function exits + if (exclusive_owner_.load(std::memory_order_acquire) + == std::this_thread::get_id()) { + return f(block_tree_data_.unsafeGet()); + } + return block_tree_data_.exclusiveAccess( + [&f, this](BlockTreeData &data) { + exclusive_owner_ = std::this_thread::get_id(); + qtils::FinalAction reset([&] { + exclusive_owner_ = decltype(std::this_thread::get_id()){}; + }); + return f(data); + }); + } + + template + decltype(auto) sharedAccess(F &&f) const { + // if this thread owns the mutex, it shall + // not be unlocked until this function exits + if (exclusive_owner_.load(std::memory_order_acquire) + == std::this_thread::get_id()) { + return f(block_tree_data_.unsafeGet()); + } + return block_tree_data_.sharedAccess(std::forward(f)); + } + + private: + se::utils::SafeObject block_tree_data_; + std::atomic exclusive_owner_ = + decltype(std::this_thread::get_id()){}; + }; + + log::Logger log_; + std::shared_ptr se_manager_; + + SafeBlockTreeData block_tree_data_; + + // Metrics + metrics::GaugeHelper metric_best_block_height_; + metrics::GaugeHelper metric_finalized_block_height_; + metrics::GaugeHelper metric_known_chain_leaves_; + }; + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/block_tree_initializer.cpp b/src/blockchain/impl/block_tree_initializer.cpp new file mode 100644 index 0000000..d895929 --- /dev/null +++ b/src/blockchain/impl/block_tree_initializer.cpp @@ -0,0 +1,295 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include "blockchain/impl/block_tree_initializer.hpp" + +#include +#include + +#include + +#include "blockchain/block_storage.hpp" +#include "blockchain/block_storage_error.hpp" +#include "blockchain/block_tree_error.hpp" +#include "log/logger.hpp" +#include "types/justification.hpp" + +namespace lean::blockchain { + + namespace { + /// Function-helper for loading (and repair if it needed) of leaves + outcome::result> loadLeaves( + const qtils::SharedRef &storage, const log::Logger &log) { + std::set block_tree_leaves; + { + auto block_tree_unordered_leaves_res = storage->getBlockTreeLeaves(); + if (block_tree_unordered_leaves_res.has_value()) { + auto &block_tree_unordered_leaves = + block_tree_unordered_leaves_res.value(); + SL_TRACE(log, + "List of leaves has loaded: {} leaves", + block_tree_unordered_leaves.size()); + + for (auto &hash : block_tree_unordered_leaves) { + // get slot of block by hash + const auto header = storage->getBlockHeader(hash); + if (not header) { + if (header + == outcome::failure(BlockTreeError::HEADER_NOT_FOUND)) { + SL_TRACE(log, "Leaf {} not found", hash); + continue; + } + SL_ERROR(log, "Leaf {} is corrupted: {}", hash, header.error()); + return header.as_failure(); + } + auto slot = header.value().slot; + SL_TRACE(log, "Leaf {} found", BlockIndex(slot, hash)); + block_tree_leaves.emplace(slot, hash); + } + } else if (false // No try to repair + or block_tree_unordered_leaves_res.error() + != BlockStorageError::BLOCK_TREE_LEAVES_NOT_FOUND) { + return block_tree_unordered_leaves_res.error(); + } + } + + if (block_tree_leaves.empty()) { + OUTCOME_TRY(it, storage->seekLastSlot()); + if (not it.isValid()) { + SL_WARN(log, "No one block was found"); + return BlockStorageError::NO_BLOCKS_FOUND; + } + + SL_WARN(log, "No one leaf was found. Trying to repair"); + while (it.isValid()) { + SL_WARN(log, "Found block at slot {}", it.slot()); + --it; + } + + Slot slot = 0; + auto lower = std::numeric_limits::min(); + auto upper = std::numeric_limits::max(); + + for (;;) { + slot = lower + (upper - lower) / 2 + 1; + + auto hashes_opt_res = storage->getBlockHash(slot); + if (hashes_opt_res.has_failure()) { + SL_CRITICAL(log, + "Search best block has failed: {}", + hashes_opt_res.error()); + return BlockTreeError::HEADER_NOT_FOUND; + } + const auto &hashes = hashes_opt_res.value(); + + if (not hashes.empty()) { + SL_TRACE(log, "bisect {} -> found", slot); + lower = slot; + } else { + SL_TRACE(log, "bisect {} -> not found", slot); + upper = slot - 1; + } + if (lower == upper) { + slot = lower; + break; + } + } + + OUTCOME_TRY(hashes, storage->getBlockHash(slot)); + for (auto &hash : hashes) { + block_tree_leaves.emplace(slot, hash); + } + + if (auto res = storage->setBlockTreeLeaves(hashes); res.has_error()) { + SL_CRITICAL( + log, "Can't save recovered block tree leaves: {}", res.error()); + return res.as_failure(); + } + } + + return block_tree_leaves; + } + } // namespace + + BlockTreeInitializer::BlockTreeInitializer( + qtils::SharedRef logsys, + qtils::SharedRef storage) { + auto logger = logsys->getLogger("BlockTree", "block_tree"); + + // Load (or recalculate) leaves + auto block_tree_leaves_res = loadLeaves(storage, logger); + if (block_tree_leaves_res.has_error()) { + SL_CRITICAL(logger, + "Failed to load block tree leaves: {}", + block_tree_leaves_res.error()); + qtils::raise(block_tree_leaves_res.error()); + } + + // Ensure if a list of leaves is empty + auto &block_tree_leaves = block_tree_leaves_res.value(); + if (block_tree_leaves.empty()) { + SL_CRITICAL(logger, "Not found any block tree leaves"); + qtils::raise(BlockTreeError::BLOCK_TREE_CORRUPTED); + } + + // Get the last finalized block + auto last_finalized_block_index_res = storage->getLastFinalized(); + if (last_finalized_block_index_res.has_error()) { + SL_CRITICAL(logger, + "Failed to get last finalized block info: {}", + last_finalized_block_index_res.error()); + qtils::raise(last_finalized_block_index_res.error()); + } + auto &last_finalized_block_index = last_finalized_block_index_res.value(); + + // Get its header + auto finalized_block_header_res = + storage->getBlockHeader(last_finalized_block_index.hash); + if (finalized_block_header_res.has_error()) { + SL_CRITICAL(logger, + "Failed to get last finalized block header: {}", + finalized_block_header_res.error()); + qtils::raise(finalized_block_header_res.error()); + } + // auto &finalized_block_header = finalized_block_header_res.value(); + // + // // call chain_events_engine->notify to init babe_config_repo preventive + // chain_events_engine->notify( + // events::ChainEventType::kFinalizedHeads, + // finalized_block_header); + + // // Ensure if last_finalized_block_info has the necessary justifications + // OUTCOME_TRY(storage->getJustification(last_finalized_block_info.hash)); + + // Last known block + auto last_known_block = *block_tree_leaves.rbegin(); + SL_INFO(logger, + "Last known block: {}, Last finalized: {}", + last_known_block, + last_finalized_block_index); + + // Load non-finalized block from block storage + std::map collected; + + { + std::unordered_set observed; + std::unordered_set dead; + // Iterate leaves + for (auto &leaf : block_tree_leaves) { + std::unordered_set subchain; + // Iterate subchain from leaf to finalized or early observer + for (auto block = leaf;;) { + // Met last finalized + if (block.hash == last_finalized_block_index.hash) { + break; + } + + // Met early observed block + if (observed.contains(block.hash)) { + break; + } + + // Met known dead block + if (dead.contains(block)) { + dead.insert(subchain.begin(), subchain.end()); + break; + } + + // Check if non-pruned fork has detected + if (block.slot == last_finalized_block_index.slot) { + dead.insert(subchain.begin(), subchain.end()); + + auto main = last_finalized_block_index; + auto fork = block; + + // Collect as the dead all blocks that differ from the finalized + // chain + for (;;) { + dead.emplace(fork); + + auto f_res = storage->getBlockHeader(fork.hash); + if (f_res.has_error()) { + break; + } + const auto &fork_header = f_res.value(); + + auto m_res = storage->getBlockHeader(main.hash); + if (m_res.has_error()) { + break; + } + const auto &main_header = m_res.value(); + + BOOST_ASSERT(fork_header.slot == main_header.slot); + if (fork_header.parent_root == main_header.parent_root) { + break; + } + + fork = {fork_header.slot, fork_header.hash()}; + main = {main_header.slot, main_header.hash()}; + } + + break; + } + + subchain.emplace(block); + + auto header_res = storage->getBlockHeader(block.hash); + if (header_res.has_error()) { + SL_CRITICAL( + logger, + "Can't get header of existing non-finalized block {}: {}", + block, + header_res.error()); + qtils::raise(BlockTreeError::BLOCK_TREE_CORRUPTED); + } + + observed.emplace(block.hash); + + auto &header = header_res.value(); + if (header.slot < last_finalized_block_index.slot) { + SL_WARN(logger, + "Detected a leaf {} lower than the last finalized block {}", + block, + last_finalized_block_index); + break; + } + + auto [it, ok] = collected.emplace(block, std::move(header)); + + block = {it->second.slot, it->second.hash()}; + } + } + + if (not dead.empty()) { + SL_WARN(logger, + "Found {} orphan blocks; " + "these block will be removed for consistency", + dead.size()); + for (auto &block : dead) { + collected.erase(block); + std::ignore = storage->removeBlock(block.hash); + } + } + } + + // Prepare and create a block tree basing last finalized block + SL_DEBUG(logger, "Last finalized block {}", last_finalized_block_index); + + last_finalized_ = last_finalized_block_index; + non_finalized_ = std::move(collected); + } + + std::tuple> + BlockTreeInitializer::nonFinalizedSubTree() { + // if (used_.test_and_set()) { + // qtils::raise(BlockTreeError::WRONG_WORKFLOW); + // } + // return std::make_tuple(last_finalized_, std::move(non_finalized_)); + return std::make_tuple(last_finalized_, non_finalized_); + } + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/block_tree_initializer.hpp b/src/blockchain/impl/block_tree_initializer.hpp new file mode 100644 index 0000000..f341ab5 --- /dev/null +++ b/src/blockchain/impl/block_tree_initializer.hpp @@ -0,0 +1,36 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +namespace lean::log { + class LoggingSystem; +} +namespace lean::blockchain { + class BlockStorage; +} + +namespace lean::blockchain { + + class BlockTreeInitializer final : Singleton { + public: + BlockTreeInitializer(qtils::SharedRef logsys, + qtils::SharedRef storage); + + std::tuple> + nonFinalizedSubTree(); + + private: + std::atomic_flag used_; + BlockIndex last_finalized_; + std::map non_finalized_; + }; + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/cached_tree.cpp b/src/blockchain/impl/cached_tree.cpp new file mode 100644 index 0000000..0781229 --- /dev/null +++ b/src/blockchain/impl/cached_tree.cpp @@ -0,0 +1,335 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "blockchain/impl/cached_tree.hpp" + +#include +#include +#include + +#include + +namespace lean::blockchain { + bool Reorg::empty() const { + return revert.empty() and apply.empty(); + } + + TreeNode::TreeNode(const BlockIndex &info) : index{info} {} + + TreeNode::TreeNode(const BlockIndex &info, + const qtils::SharedRef &parent) + : index{info}, + weak_parent{std::shared_ptr(parent)}, + depth(parent->depth + 1), + reverted{parent->reverted} {} + + std::shared_ptr TreeNode::parent() const { + return weak_parent.lock(); + } + + void TreeNode::detach() { + return weak_parent.reset(); + } + + BlockWeight TreeNode::weight() const { + return index.slot; + } + + Reorg reorg(qtils::SharedRef from, qtils::SharedRef to) { + Reorg reorg; + while (from != to) { + if (from->index.slot > to->index.slot) { + reorg.revert.emplace_back(from->index); + from = from->parent(); + [[unlikely]] if (not from) { throw std::bad_weak_ptr{}; } + + } else { + reorg.apply.emplace_back(to->index); + to = to->parent(); + [[unlikely]] if (not from) { throw std::bad_weak_ptr{}; } + } + } + reorg.common = to->index; + std::ranges::reverse(reorg.apply); + return reorg; + } + + template + bool descend(qtils::SharedRef from, + const qtils::SharedRef &to, + const F &f) { + while (from != to) { + if (from->index.slot <= to->index.slot) { + return false; + } + f(from); + from = from->parent(); + [[unlikely]] if (not from) { throw std::bad_weak_ptr{}; } + } + return true; + } + + bool canDescend(const qtils::SharedRef &from, + const qtils::SharedRef &to) { + return descend(from, to, [](const qtils::SharedRef &) {}); + } + + bool CachedTree::chooseBest(const qtils::SharedRef &node) { + if (node->reverted) { + return false; + } + BOOST_ASSERT(not best_->reverted); + if (node->weight() > best_->weight()) { + best_ = node; + return true; + } + return false; + } + + struct Cmp { + bool operator()(const qtils::SharedRef &lhs, + const qtils::SharedRef &rhs) const { + BOOST_ASSERT(lhs and rhs); + return rhs->index.slot < lhs->index.slot; + } + }; + + void CachedTree::forceRefreshBest() { + std::set, Cmp> candidates; + for (const auto &key : leaves_ | std::views::keys) { + auto node = find(key); + BOOST_ASSERT(node.has_value()); + candidates.emplace(std::move(node.value())); + } + + best_ = root_; + while (not candidates.empty()) { + auto node = candidates.extract(candidates.begin()); + BOOST_ASSERT(not node.empty()); + + auto &tree_node = node.value(); + if (tree_node->reverted) { + if (auto parent = tree_node->parent()) { + candidates.emplace(std::move(parent)); + } + continue; + } + + if (best_->weight() < tree_node->weight()) { + best_ = tree_node; + } + } + } + + CachedTree::CachedTree(const BlockIndex &root) + : root_{std::make_shared(root)}, + best_{root_}, + nodes_{{root.hash, root_}} { + leaves_.emplace(root.hash, root.slot); + } + + BlockIndex CachedTree::finalized() const { + return root_->index; + } + + BlockIndex CachedTree::best() const { + return best_->index; + } + + size_t CachedTree::leafCount() const { + return leaves_.size(); + } + + std::vector CachedTree::leafInfo() const { + std::vector output; + output.reserve(leaves_.size()); + std::ranges::transform( + leaves_, std::back_inserter(output), [](const auto &v) { + return BlockIndex(v.second, v.first); + }); + return output; + } + + std::vector CachedTree::leafHashes() const { + std::vector output; + output.reserve(leaves_.size()); + std::ranges::transform(leaves_, + std::back_inserter(output), + [](const auto &v) { return v.first; }); + return output; + } + + bool CachedTree::isLeaf(const BlockHash &hash) const { + return leaves_.contains(hash); + } + + BlockIndex CachedTree::bestWith( + const qtils::SharedRef &required) const { + std::set, Cmp> candidates; + for (const auto &leaf : leaves_ | std::views::keys) { + auto node_opt = find(leaf); + BOOST_ASSERT(node_opt.has_value()); + candidates.emplace(std::move(node_opt.value())); + } + auto best = required; + while (not candidates.empty()) { + auto _node = candidates.extract(candidates.begin()); + auto &node = _node.value(); + if (node->index.slot <= required->index.slot) { + continue; + } + if (node->reverted) { + if (auto parent = node->parent()) { + candidates.emplace(std::move(parent)); + } + continue; + } + if (node->weight() > best->weight()) { + if (canDescend(node, required)) { + best = node; + } + } + } + return best->index; + } + + std::optional> CachedTree::find( + const BlockHash &hash) const { + if (auto it = nodes_.find(hash); it != nodes_.end()) { + return it->second; + } + return std::nullopt; + } + + std::optional CachedTree::add( + const qtils::SharedRef &new_node) { + if (nodes_.contains(new_node->index.hash)) { + return std::nullopt; + } + BOOST_ASSERT(new_node->children.empty()); + auto parent = new_node->parent(); + [[unlikely]] if (not parent) { throw std::bad_weak_ptr{}; } + BOOST_ASSERT(not std::ranges::contains(parent->children, new_node)); + + new_node->depth = parent->depth + 1; + + parent->children.emplace_back(new_node); + nodes_.emplace(new_node->index.hash, new_node); + leaves_.erase(parent->index.hash); + leaves_.emplace(new_node->index.hash, new_node->index.slot); + if (not new_node->reverted and new_node->weight() > best_->weight()) { + auto old_best = best_; + best_ = new_node; + return reorg(old_best, best_); + } + return std::nullopt; + } + + ReorgAndPrune CachedTree::finalize( + const qtils::SharedRef &new_finalized) { + BOOST_ASSERT(new_finalized->index.slot >= root_->index.slot); + if (new_finalized == root_) { + return {}; + } + BOOST_ASSERT(new_finalized->parent()); + ReorgAndPrune changes; + if (not canDescend(best_, new_finalized)) { + changes.reorg = reorg(best_, new_finalized); + } + std::deque> queue; + for (std::shared_ptr finalized_child = new_finalized, + parent = finalized_child->parent(); + parent; + finalized_child = parent, parent = parent->parent()) { + for (auto &child : parent->children) { + if (child == finalized_child) { + continue; + } + queue.emplace_back(child); + } + parent->children.clear(); + nodes_.erase(parent->index.hash); + } + while (not queue.empty()) { + auto parent = std::move(queue.front()); + queue.pop_front(); + changes.prune.emplace_back(parent->index); + for (auto &child : parent->children) { + queue.emplace_back(child); + } + if (parent->children.empty()) { + leaves_.erase(parent->index.hash); + } + parent->children.clear(); + nodes_.erase(parent->index.hash); + } + std::ranges::reverse(changes.prune); + root_ = new_finalized; + root_->detach(); + if (changes.reorg) { + forceRefreshBest(); + size_t offset = changes.reorg->apply.size(); + [[maybe_unused]] auto ok = descend( + best_, new_finalized, [&](const std::shared_ptr &node) { + changes.reorg->apply.emplace_back(node->index); + }); + BOOST_ASSERT(ok); + std::reverse( + // NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions) + changes.reorg->apply.begin() + offset, + changes.reorg->apply.end()); + } + return changes; + } + + ReorgAndPrune CachedTree::removeLeaf(const BlockHash &hash) { + ReorgAndPrune changes; + auto node_it = nodes_.find(hash); + BOOST_ASSERT(node_it != nodes_.end()); + auto &node = node_it->second; + BOOST_ASSERT(node); + auto leaf_it = leaves_.find(hash); + BOOST_ASSERT(leaf_it != leaves_.end()); + BOOST_ASSERT(node->children.empty()); + auto parent = node->parent(); + [[unlikely]] if (not parent) { throw std::bad_weak_ptr{}; } + auto child_it = std::ranges::find(parent->children, node); + BOOST_ASSERT(child_it != parent->children.end()); + changes.prune.emplace_back(node->index); + parent->children.erase(child_it); + if (parent->children.empty()) { + leaves_.emplace(parent->index.hash, parent->index.slot); + } + leaves_.erase(leaf_it); + if (node == best_) { + auto old_best = node; + forceRefreshBest(); + changes.reorg = reorg(old_best, best_); + } + nodes_.erase(node_it); + return changes; + } + + ReorgAndPrune CachedTree::removeUnfinalized() { + ReorgAndPrune changes; + if (best_ != root_) { + changes.reorg = reorg(best_, root_); + } + std::deque> queue{root_}; + while (not queue.empty()) { + auto parent = std::move(queue.front()); + queue.pop_front(); + for (auto &child : parent->children) { + changes.prune.emplace_back(child->index); + queue.emplace_back(child); + } + parent->children.clear(); + } + std::ranges::reverse(changes.prune); + *this = CachedTree{root_->index}; + return changes; + } +} // namespace lean::blockchain diff --git a/src/blockchain/impl/cached_tree.hpp b/src/blockchain/impl/cached_tree.hpp new file mode 100644 index 0000000..29df66a --- /dev/null +++ b/src/blockchain/impl/cached_tree.hpp @@ -0,0 +1,113 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "types/block_index.hpp" + +namespace lean::blockchain { + using BlockWeight = std::tuple; + + /** + * Used to update hashes of best chain by number. + */ + struct Reorg { + BlockIndex common; + std::vector revert; + std::vector apply; + + [[nodiscard]] bool empty() const; + }; + + /** + * Used to enqueue blocks for removal. + * Children are removed before parent. + */ + struct ReorgAndPrune { + std::optional reorg; + std::vector prune; + }; + + /** + * In-memory light representation of the tree, used for efficiency and usage + * convenience - we would only ask the database for some info, when directly + * requested + */ + class TreeNode { + public: + explicit TreeNode(const BlockIndex &info); + TreeNode(const BlockIndex &info, const qtils::SharedRef &parent); + + [[nodiscard]] std::shared_ptr parent() const; + void detach(); + [[nodiscard]] BlockWeight weight() const; + + const BlockIndex index; + + private: + std::weak_ptr weak_parent; + + public: + uint32_t depth = 0; + bool contains_approved_para_block = false; + bool reverted = false; // TODO Looks like actually unused + + std::vector> children{}; + }; + + Reorg reorg(qtils::SharedRef from, qtils::SharedRef to); + + bool canDescend(const qtils::SharedRef &from, + const qtils::SharedRef &to); + + /** + * Non-finalized part of the block tree + */ + class CachedTree { + public: + explicit CachedTree(const BlockIndex &root); + + [[nodiscard]] BlockIndex finalized() const; + [[nodiscard]] BlockIndex best() const; + [[nodiscard]] size_t leafCount() const; + [[nodiscard]] std::vector leafHashes() const; + [[nodiscard]] std::vector leafInfo() const; + [[nodiscard]] bool isLeaf(const BlockHash &hash) const; + [[nodiscard]] BlockIndex bestWith( + const qtils::SharedRef &required) const; + [[nodiscard]] std::optional> find( + const BlockHash &hash) const; + std::optional add(const qtils::SharedRef &new_node); + ReorgAndPrune finalize(const qtils::SharedRef &new_finalized); + + /** + * Can't remove finalized root. + */ + ReorgAndPrune removeLeaf(const BlockHash &hash); + + /** + * Used when switching from fast-sync to full-sync. + */ + ReorgAndPrune removeUnfinalized(); + + /// Force find and update the actual best block + void forceRefreshBest(); + + private: + /** + * Compare node weight with best and replace if heavier. + * @return true if heavier and replaced. + */ + bool chooseBest(const qtils::SharedRef &node); + + qtils::SharedRef root_; + qtils::SharedRef best_; + std::unordered_map> nodes_; + std::unordered_map leaves_; + }; +} // namespace lean::blockchain diff --git a/src/blockchain/impl/genesis_block_header_impl.cpp b/src/blockchain/impl/genesis_block_header_impl.cpp index 8559ff0..a7c47f0 100644 --- a/src/blockchain/impl/genesis_block_header_impl.cpp +++ b/src/blockchain/impl/genesis_block_header_impl.cpp @@ -18,16 +18,13 @@ namespace lean::blockchain { const qtils::SharedRef &logsys, const qtils::SharedRef &chain_spec, const qtils::SharedRef &hasher) { - scale::impl::memory::DecoderFromSpan decoder(chain_spec->genesisHeader(), - test_vectors::config::tiny); - try { - decode(static_cast(*this), decoder); - } catch (std::system_error &e) { + auto res = encode(chain_spec->genesisHeader()); + if (res.has_error()) { auto logger = logsys->getLogger("ChainSpec", "application"); SL_CRITICAL(logger, "Failed to decode genesis block header from chain spec: {}", - e.code()); - qtils::raise(e.code()); + res.error()); + qtils::raise_on_err(res); } updateHash(*hasher); } diff --git a/src/blockchain/impl/storage_util.cpp b/src/blockchain/impl/storage_util.cpp index 3ef13f1..f45a116 100644 --- a/src/blockchain/impl/storage_util.cpp +++ b/src/blockchain/impl/storage_util.cpp @@ -11,48 +11,18 @@ #include "storage/storage_error.hpp" using qtils::ByteVec; -// using lean::Hash256; -// using lean::primitives::BlockId; -// using lean::primitives::BlockNumber; -// using lean::storage::Space; +// using jam::Hash256; +// using jam::primitives::BlockId; +// using jam::primitives::Slot; +// using jam::storage::Space; namespace lean::blockchain { - outcome::result> blockIdToBlockHash( - storage::SpacedStorage &storage, const BlockId &block_id) { - return visit_in_place( - block_id, - [&](const BlockNumber &block_number) - -> outcome::result> { - auto key_space = storage.getSpace(storage::Space::LookupKey); - return key_space->tryGet(slotToHashLookupKey(block_number)); - }, - [](const BlockHash &block_hash) { - return std::make_optional(ByteVec(block_hash)); - }); - } - - outcome::result> blockHashByNumber( - storage::SpacedStorage &storage, BlockNumber block_number) { - auto key_space = storage.getSpace(storage::Space::LookupKey); - OUTCOME_TRY(data_opt, key_space->tryGet(slotToHashLookupKey(block_number))); - if (data_opt.has_value()) { - OUTCOME_TRY(hash, BlockHash::fromSpan(data_opt.value())); - return hash; - } - return std::nullopt; - } - outcome::result hasInSpace(storage::SpacedStorage &storage, storage::Space space, - const BlockId &block_id) { - OUTCOME_TRY(key, blockIdToBlockHash(storage, block_id)); - if (not key.has_value()) { - return false; - } - + const BlockHash &block_hash) { auto target_space = storage.getSpace(space); - return target_space->contains(key.value()); + return target_space->contains(block_hash); } outcome::result putToSpace(storage::SpacedStorage &storage, diff --git a/src/blockchain/impl/storage_util.hpp b/src/blockchain/impl/storage_util.hpp index c4baf52..6721239 100644 --- a/src/blockchain/impl/storage_util.hpp +++ b/src/blockchain/impl/storage_util.hpp @@ -8,7 +8,7 @@ #include -#include "lean_types/types.hpp" +#include "types/types.hpp" // #include "primitives/block_id.hpp" #include "serde/serialization.hpp" @@ -26,7 +26,7 @@ * * There is also an auxilary space named Space::kLookupKey where * BlockId->NumHashKey mappings are stored. Effectively there are could be two - * types of mappings: either BlockNumber->NumHashKey or BlockHash->NumHashKey. + * types of mappings: either Slot->NumHashKey or BlockHash->NumHashKey. * Anyways, the resulting NumHashKey is good to be used for further manipulating * with the Block in other storage spaces. */ @@ -53,18 +53,18 @@ namespace lean::blockchain { * Returns block hash by number if any */ outcome::result> blockHashByNumber( - storage::SpacedStorage &storage, BlockNumber block_number); + storage::SpacedStorage &storage, Slot slot); /** * Check if an entry is contained in the database * @param storage - to get the entry from * @param space - key space in the storage to which the entry belongs - * @param block_id - id of the block to get entry for + * @param block_hash - hash of the block to get entry for * @return true if the entry exists, false if does not, and error at fail */ outcome::result hasInSpace(storage::SpacedStorage &storage, storage::Space space, - const BlockId &block_id); + const BlockHash &block_hash); /** * Put an entry to the key space \param space diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index b4ff0d7..c43765a 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -5,7 +5,7 @@ # #add_subdirectory(blake2) -#add_subdirectory(sha) +add_subdirectory(sha) #add_subdirectory(twox) add_library(hasher @@ -15,6 +15,6 @@ target_link_libraries(hasher qtils::qtils # blake2 # twox -# sha + sha # keccak ) diff --git a/src/crypto/hasher.hpp b/src/crypto/hasher.hpp index 04ec76f..84ca9ce 100644 --- a/src/crypto/hasher.hpp +++ b/src/crypto/hasher.hpp @@ -16,68 +16,68 @@ namespace lean::crypto { public: virtual ~Hasher() = default; - /** - * @brief twox_64 calculates 8-byte twox hash - * @param data source data - * @return 64-bit hash value - */ - virtual Hash64 twox_64(qtils::ByteView data) const = 0; - - /** - * @brief blake2b_64 function calculates 8-byte blake2b hash - * @param data source value - * @return 64-bit hash value - */ - virtual Hash64 blake2b_64(qtils::ByteView data) const = 0; - - /** - * @brief twox_128 calculates 16-byte twox hash - * @param data source data - * @return 128-bit hash value - */ - virtual Hash128 twox_128(qtils::ByteView data) const = 0; - - /** - * @brief blake2b_128 function calculates 16-byte blake2b hash - * @param data source value - * @return 128-bit hash value - */ - virtual Hash128 blake2b_128(qtils::ByteView data) const = 0; - - /** - * @brief twox_256 calculates 32-byte twox hash - * @param data source data - * @return 256-bit hash value - */ - virtual Hash256 twox_256(qtils::ByteView data) const = 0; - - /** - * @brief blake2b_256 function calculates 32-byte blake2b hash - * @param data source value - * @return 256-bit hash value - */ - virtual Hash256 blake2b_256(qtils::ByteView data) const = 0; - - /** - * @brief blake2b_512 function calculates 64-byte blake2b hash - * @param data source value - * @return 512-bit hash value - */ - virtual Hash512 blake2b_512(qtils::ByteView data) const = 0; - - /** - * @brief keccak_256 function calculates 32-byte keccak hash - * @param data source value - * @return 256-bit hash value - */ - virtual Hash256 keccak_256(qtils::ByteView data) const = 0; - - /** - * @brief blake2s_256 function calculates 32-byte blake2s hash - * @param data source value - * @return 256-bit hash value - */ - virtual Hash256 blake2s_256(qtils::ByteView data) const = 0; + // /** + // * @brief twox_64 calculates 8-byte twox hash + // * @param data source data + // * @return 64-bit hash value + // */ + // virtual Hash64 twox_64(qtils::ByteView data) const = 0; + // + // /** + // * @brief blake2b_64 function calculates 8-byte blake2b hash + // * @param data source value + // * @return 64-bit hash value + // */ + // virtual Hash64 blake2b_64(qtils::ByteView data) const = 0; + // + // /** + // * @brief twox_128 calculates 16-byte twox hash + // * @param data source data + // * @return 128-bit hash value + // */ + // virtual Hash128 twox_128(qtils::ByteView data) const = 0; + // + // /** + // * @brief blake2b_128 function calculates 16-byte blake2b hash + // * @param data source value + // * @return 128-bit hash value + // */ + // virtual Hash128 blake2b_128(qtils::ByteView data) const = 0; + // + // /** + // * @brief twox_256 calculates 32-byte twox hash + // * @param data source data + // * @return 256-bit hash value + // */ + // virtual Hash256 twox_256(qtils::ByteView data) const = 0; + // + // /** + // * @brief blake2b_256 function calculates 32-byte blake2b hash + // * @param data source value + // * @return 256-bit hash value + // */ + // virtual Hash256 blake2b_256(qtils::ByteView data) const = 0; + // + // /** + // * @brief blake2b_512 function calculates 64-byte blake2b hash + // * @param data source value + // * @return 512-bit hash value + // */ + // virtual Hash512 blake2b_512(qtils::ByteView data) const = 0; + // + // /** + // * @brief keccak_256 function calculates 32-byte keccak hash + // * @param data source value + // * @return 256-bit hash value + // */ + // virtual Hash256 keccak_256(qtils::ByteView data) const = 0; + // + // /** + // * @brief blake2s_256 function calculates 32-byte blake2s hash + // * @param data source value + // * @return 256-bit hash value + // */ + // virtual Hash256 blake2s_256(qtils::ByteView data) const = 0; /** * @brief sha2_256 function calculates 32-byte sha2-256 hash diff --git a/src/crypto/hasher/hasher_impl.cpp b/src/crypto/hasher/hasher_impl.cpp index 6698000..27d24d1 100644 --- a/src/crypto/hasher/hasher_impl.cpp +++ b/src/crypto/hasher/hasher_impl.cpp @@ -11,7 +11,7 @@ // #include "crypto/blake2/blake2b.h" // #include "crypto/blake2/blake2s.h" // #include "crypto/keccak/keccak.hpp" -// #include "crypto/sha/sha256.hpp" +#include "crypto/sha/sha256.hpp" // #include "crypto/twox/twox.hpp" namespace lean::crypto { @@ -60,9 +60,9 @@ namespace lean::crypto { // blake2s(out.data(), 32, nullptr, 0, data.data(), data.size()); // return out; // } - // - // Hash256 HasherImpl::sha2_256(qtils::ByteView data) const { - // return sha256(data); - // } + + Hash256 HasherImpl::sha2_256(qtils::ByteView data) const { + return sha256(data); + } } // namespace lean::crypto diff --git a/src/crypto/hasher/hasher_impl.hpp b/src/crypto/hasher/hasher_impl.hpp index 22d41a5..b43877f 100644 --- a/src/crypto/hasher/hasher_impl.hpp +++ b/src/crypto/hasher/hasher_impl.hpp @@ -15,25 +15,25 @@ namespace lean::crypto { public: ~HasherImpl() override = default; - Hash64 twox_64(qtils::ByteView data) const override; - - Hash64 blake2b_64(qtils::ByteView data) const override; - - Hash128 twox_128(qtils::ByteView data) const override; - - Hash128 blake2b_128(qtils::ByteView data) const override; - - Hash256 twox_256(qtils::ByteView data) const override; - - Hash256 blake2b_256(qtils::ByteView data) const override; - - Hash256 keccak_256(qtils::ByteView data) const override; - - Hash256 blake2s_256(qtils::ByteView data) const override; + // Hash64 twox_64(qtils::ByteView data) const override; + // + // Hash64 blake2b_64(qtils::ByteView data) const override; + // + // Hash128 twox_128(qtils::ByteView data) const override; + // + // Hash128 blake2b_128(qtils::ByteView data) const override; + // + // Hash256 twox_256(qtils::ByteView data) const override; + // + // Hash256 blake2b_256(qtils::ByteView data) const override; + // + // Hash256 keccak_256(qtils::ByteView data) const override; + // + // Hash256 blake2s_256(qtils::ByteView data) const override; Hash256 sha2_256(qtils::ByteView data) const override; - Hash512 blake2b_512(qtils::ByteView data) const override; + // Hash512 blake2b_512(qtils::ByteView data) const override; }; } // namespace lean::crypto diff --git a/src/executable/CMakeLists.txt b/src/executable/CMakeLists.txt index 3e6813f..9897ab9 100644 --- a/src/executable/CMakeLists.txt +++ b/src/executable/CMakeLists.txt @@ -23,22 +23,22 @@ if (CMAKE_HOST_APPLE) option(MACOS_BIG_EXE_USE_DYLIB "compile dylib instead of exe to work around dyld cache error when loading big exe" ON) endif () if (MACOS_BIG_EXE_USE_DYLIB) - add_library(jam_node SHARED jam_node.cpp) - set_target_properties(jam_node PROPERTIES PREFIX "" DEBUG_POSTFIX "") - target_compile_definitions(jam_node PRIVATE BUILD_AS_LIBRARY) - target_link_libraries(jam_node ${LIBRARIES}) - - add_executable(jam_node_dlopen dlopen.cpp) - set_target_properties(jam_node_dlopen PROPERTIES OUTPUT_NAME jam_node) - set_target_properties(jam_node_dlopen PROPERTIES LINKER_LANGUAGE CXX) + add_library(lean_node SHARED lean_node.cpp) + set_target_properties(lean_node PROPERTIES PREFIX "" DEBUG_POSTFIX "") + target_compile_definitions(lean_node PRIVATE BUILD_AS_LIBRARY) + target_link_libraries(lean_node ${LIBRARIES}) + + add_executable(lean_node_dlopen dlopen.cpp) + set_target_properties(lean_node_dlopen PROPERTIES OUTPUT_NAME lean_node) + set_target_properties(lean_node_dlopen PROPERTIES LINKER_LANGUAGE CXX) else () - add_executable(jam_node jam_node.cpp) - target_link_libraries(jam_node ${LIBRARIES}) + add_executable(lean_node lean_node.cpp) + target_link_libraries(lean_node ${LIBRARIES}) endif () -add_dependencies(jam_node all_modules) +add_dependencies(lean_node all_modules) #if (BACKWARD) -# add_backward(jam_node) +# add_backward(lean_node) #endif () diff --git a/src/executable/jam_node.cpp b/src/executable/lean_node.cpp similarity index 100% rename from src/executable/jam_node.cpp rename to src/executable/lean_node.cpp diff --git a/src/injector/CMakeLists.txt b/src/injector/CMakeLists.txt index 1d74086..2c69677 100644 --- a/src/injector/CMakeLists.txt +++ b/src/injector/CMakeLists.txt @@ -21,4 +21,5 @@ target_link_libraries(node_injector modules storage blockchain + timeline ) diff --git a/src/injector/node_injector.cpp b/src/injector/node_injector.cpp index 66aca6c..50b43fd 100644 --- a/src/injector/node_injector.cpp +++ b/src/injector/node_injector.cpp @@ -18,14 +18,17 @@ #include #include #include +#include #include #include "app/configuration.hpp" #include "app/impl/application_impl.hpp" #include "app/impl/chain_spec_impl.hpp" #include "app/impl/state_manager_impl.hpp" +#include "app/impl/timeline_impl.hpp" #include "app/impl/watchdog.hpp" #include "blockchain/impl/block_storage_impl.hpp" +#include "blockchain/impl/block_tree_impl.hpp" #include "blockchain/impl/genesis_block_header_impl.hpp" #include "clock/impl/clock_impl.hpp" #include "crypto/hasher/hasher_impl.hpp" @@ -40,6 +43,7 @@ #include "storage/in_memory/in_memory_spaced_storage.hpp" #include "storage/in_memory/in_memory_storage.hpp" #include "storage/rocksdb/rocksdb.hpp" +#include "types/config.hpp" namespace { namespace di = boost::di; @@ -84,6 +88,8 @@ namespace { di::bind.to(), di::bind.to(), di::bind.to(), + di::bind.to(), + di::bind.to(), // user-defined overrides... std::forward(args)...); @@ -139,6 +145,9 @@ namespace lean::injector { } else if ("NetworkingLoader" == module->get_loader_id()) { loader = pimpl_->injector_ .create>(); + } else if ("ProductionLoader" == module->get_loader_id()) { + loader = pimpl_->injector_ + .create>(); } else if ("SynchronizerLoader" == module->get_loader_id()) { loader = pimpl_->injector_ diff --git a/src/lean_types/types.hpp b/src/lean_types/types.hpp deleted file mode 100644 index 8a9bf27..0000000 --- a/src/lean_types/types.hpp +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright Quadrivium LLC - * All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include - -#include -#include - -namespace lean { - // stub types. must be refactored in future - - struct Stub {}; - - // blockchain types - - using OpaqueHash = qtils::ByteArr<32>; - - using BlockHash = OpaqueHash; - using HeaderHash = OpaqueHash; - using StateRoot = OpaqueHash; - using BodyRoot = OpaqueHash; - - using Slot = uint64_t; - - using ProposerIndex = uint64_t; - - struct BlockIndex { - Slot slot; - BlockHash hash; - auto operator<=>(const BlockIndex &other) const = default; - }; - - using BlockInfo = BlockIndex; - - using BlockNumber = Slot; - - using BlockId = std::variant; - - // networking types - - using PeerId = qtils::Tagged; // STUB - - /// Direction, in which to retrieve ordered data - enum class Direction : uint8_t { - /// from child to parent - ASCENDING = 0, - /// from parent to canonical child - DESCENDING = 1 - }; - - /// Request for blocks to another peer - struct BlocksRequest { - /// start from this block - BlockIndex from{}; - /// sequence direction - Direction direction{}; - /// maximum number of blocks to return; an implementation defined maximum is - /// used when unspecified - std::optional max{}; - bool multiple_justifications = true; - }; - - struct BlockAnnounce { - BlockAnnounce(const BlockAnnounce &) = delete; - }; - -} // namespace lean - -template <> -struct fmt::formatter { - constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(), end = ctx.end(); - if (it != end && *it != '}') { - throw format_error("invalid format"); - } - return it; - } - - template - auto format(const lean::Stub &, FormatContext &ctx) const - -> decltype(ctx.out()) { - return fmt::format_to(ctx.out(), "stub"); - } -}; - -template <> -struct fmt::formatter { - // Presentation format: 's' - short, 'l' - long. - char presentation = 's'; - - // Parses format specifications of the form ['s' | 'l']. - constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { - // Parse the presentation format and store it in the formatter: - auto it = ctx.begin(), end = ctx.end(); - if (it != end && (*it == 's' or *it == 'l')) { - presentation = *it++; - } - - // Check if reached the end of the range: - if (it != end && *it != '}') { - throw format_error("invalid format"); - } - - // Return an iterator past the end of the parsed range: - return it; - } - - // Formats the BlockInfo using the parsed format specification (presentation) - // stored in this formatter. - template - auto format(const lean::BlockInfo &block_info, FormatContext &ctx) const - -> decltype(ctx.out()) { - // ctx.out() is an output iterator to write to. - - if (presentation == 's') { - return fmt::format_to( - ctx.out(), "{:0x} @ {}", block_info.hash, block_info.slot); - } - - return fmt::format_to( - ctx.out(), "{:0xx} @ {}", block_info.hash, block_info.slot); - } -}; - - -template -struct fmt::formatter> : formatter {}; diff --git a/src/loaders/impl/example_loader.hpp b/src/loaders/impl/example_loader.hpp index 652b577..2f40869 100644 --- a/src/loaders/impl/example_loader.hpp +++ b/src/loaders/impl/example_loader.hpp @@ -6,8 +6,6 @@ #pragma once -#include - #include #include @@ -26,13 +24,16 @@ namespace lean::loaders { std::shared_ptr> on_loading_finished_; - std::shared_ptr>> + std::shared_ptr< + BaseSubscriber>> on_request_; - std::shared_ptr>> + std::shared_ptr< + BaseSubscriber>> on_response_; - std::shared_ptr>> + std::shared_ptr< + BaseSubscriber>> on_notification_; public: @@ -46,13 +47,13 @@ namespace lean::loaders { ~ExampleLoader() override = default; void start(std::shared_ptr module) override { - set_module(module); + set_module(std::move(module)); auto module_accessor = get_module() - ->getFunctionFromLibrary< - std::weak_ptr, - modules::ExampleModuleLoader &, - std::shared_ptr>("query_module_instance"); + ->getFunctionFromLibrary, + ExampleModuleLoader &, + std::shared_ptr>( + "query_module_instance"); if (not module_accessor) { return; @@ -60,7 +61,7 @@ namespace lean::loaders { auto module_internal = (*module_accessor)(*this, logsys_); - on_init_complete_ = se::SubscriberCreator::template create< + on_init_complete_ = se::SubscriberCreator::create< EventTypes::ExampleModuleIsLoaded>( *se_manager_, SubscriptionEngineHandlers::kTest, @@ -70,61 +71,63 @@ namespace lean::loaders { } }); - on_loading_finished_ = - se::SubscriberCreator::template create< - EventTypes::LoadingIsFinished>( - *se_manager_, - SubscriptionEngineHandlers::kTest, - [module_internal](auto &) { - if (auto m = module_internal.lock()) { - m->on_loading_is_finished(); - } - }); + on_loading_finished_ = se::SubscriberCreator::create< + EventTypes::LoadingIsFinished>( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &) { + if (auto m = module_internal.lock()) { + m->on_loading_is_finished(); + } + }); - on_request_ = se::SubscriberCreator>:: - template create( + on_request_ = se::SubscriberCreator>:: + create( *se_manager_, SubscriptionEngineHandlers::kTest, - [module_internal](auto &msg) { + [module_internal](auto &, auto msg) { if (auto m = module_internal.lock()) { - m->on_request(msg); + m->on_request(std::move(msg)); } }); - on_response_ = se::SubscriberCreator>:: - template create( + on_response_ = se::SubscriberCreator>:: + create( *se_manager_, SubscriptionEngineHandlers::kTest, - [module_internal](auto &msg) { + [module_internal](auto &, auto msg) { if (auto m = module_internal.lock()) { - m->on_response(msg); + m->on_response(std::move(msg)); } }); on_notification_ = - se::SubscriberCreator>:: - template create( + se::SubscriberCreator>:: + create( *se_manager_, SubscriptionEngineHandlers::kTest, - [module_internal](auto &msg) { + [module_internal](auto &, auto msg) { if (auto m = module_internal.lock()) { - m->on_notify(msg); + m->on_notify(std::move(msg)); } }); - se_manager_->notify(lean::EventTypes::ExampleModuleIsLoaded); + se_manager_->notify(EventTypes::ExampleModuleIsLoaded); } - void dispatch_request(std::shared_ptr s) override { - se_manager_->notify(lean::EventTypes::ExampleRequest, s); + void dispatch_request(std::shared_ptr msg) override { + se_manager_->notify(EventTypes::ExampleRequest, msg); } - void dispatch_response(std::shared_ptr s) override { - se_manager_->notify(lean::EventTypes::ExampleResponse, s); + void dispatch_response(std::shared_ptr msg) override { + se_manager_->notify(EventTypes::ExampleResponse, msg); } - void dispatch_notify(std::shared_ptr s) override { - se_manager_->notify(lean::EventTypes::ExampleNotification, s); + void dispatch_notify(std::shared_ptr msg) override { + se_manager_->notify(EventTypes::ExampleNotification, msg); } }; } // namespace lean::loaders diff --git a/src/loaders/impl/production_loader.hpp b/src/loaders/impl/production_loader.hpp new file mode 100644 index 0000000..70bc090 --- /dev/null +++ b/src/loaders/impl/production_loader.hpp @@ -0,0 +1,136 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "loaders/loader.hpp" +#include "log/logger.hpp" +#include "modules/production/production.hpp" +#include "se/subscription.hpp" + +namespace lean::loaders { + + class ProductionLoader final + : public std::enable_shared_from_this, + public Loader, + public modules::ProductionLoader { + qtils::SharedRef block_tree_; + qtils::SharedRef hasher_; + + std::shared_ptr> on_init_complete_; + + std::shared_ptr> on_loading_finished_; + + std::shared_ptr< + BaseSubscriber>> + on_slot_started_; + std::shared_ptr< + BaseSubscriber>> + on_leave_update_; + std::shared_ptr>> + on_block_finalized_; + + public: + ProductionLoader(qtils::SharedRef logsys, + qtils::SharedRef se_manager, + qtils::SharedRef block_tree, + qtils::SharedRef hasher) + : Loader(std::move(logsys), std::move(se_manager)), + block_tree_(std::move(block_tree)), + hasher_(std::move(hasher)) {} + + ProductionLoader(const ProductionLoader &) = delete; + ProductionLoader &operator=(const ProductionLoader &) = delete; + + ~ProductionLoader() override = default; + + void start(std::shared_ptr module) override { + set_module(module); + auto module_accessor = + get_module() + ->getFunctionFromLibrary, + modules::ProductionLoader &, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr>( + "query_module_instance"); + + if (not module_accessor) { + return; + } + + auto module_internal = + (*module_accessor)(*this, logsys_, block_tree_, hasher_); + + on_init_complete_ = se::SubscriberCreator::create< + EventTypes::ProductionIsLoaded>( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &) { + if (auto m = module_internal.lock()) { + m->on_loaded_success(); + } + }); + + on_loading_finished_ = se::SubscriberCreator::create< + EventTypes::LoadingIsFinished>( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &) { + if (auto m = module_internal.lock()) { + m->on_loading_is_finished(); + } + }); + + on_slot_started_ = + se::SubscriberCreator>:: + create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &, auto msg) { + if (auto m = module_internal.lock()) { + m->on_slot_started(std::move(msg)); + } + }); + + on_leave_update_ = + se::SubscriberCreator>:: + create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &, auto msg) { + if (auto m = module_internal.lock()) { + m->on_leave_update(std::move(msg)); + } + }); + + on_block_finalized_ = + se::SubscriberCreator>:: + create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &, auto msg) { + if (auto m = module_internal.lock()) { + m->on_block_finalized(std::move(msg)); + } + }); + + se_manager_->notify(EventTypes::ProductionIsLoaded); + } + + void dispatch_block_produced(std::shared_ptr msg) override { + se_manager_->notify(EventTypes::BlockProduced, msg); + } + }; + +} // namespace lean::loaders diff --git a/src/loaders/impl/synchronizer_loader.hpp b/src/loaders/impl/synchronizer_loader.hpp index 122f2c8..e337ab3 100644 --- a/src/loaders/impl/synchronizer_loader.hpp +++ b/src/loaders/impl/synchronizer_loader.hpp @@ -6,8 +6,6 @@ #pragma once -#include - #include #include @@ -52,10 +50,10 @@ namespace lean::loaders { set_module(module); auto module_accessor = get_module() - ->getFunctionFromLibrary< - std::weak_ptr, - modules::SynchronizerLoader &, - std::shared_ptr>("query_module_instance"); + ->getFunctionFromLibrary, + modules::SynchronizerLoader &, + std::shared_ptr>( + "query_module_instance"); if (not module_accessor) { return; @@ -63,7 +61,7 @@ namespace lean::loaders { auto module_internal = (*module_accessor)(*this, logsys_); - on_init_complete_ = se::SubscriberCreator::template create< + on_init_complete_ = se::SubscriberCreator::create< EventTypes::SynchronizerIsLoaded>( *se_manager_, SubscriptionEngineHandlers::kTest, @@ -77,7 +75,7 @@ namespace lean::loaders { on_block_announce_ = se::SubscriberCreator< qtils::Empty, std::shared_ptr>:: - template create( + create( *se_manager_, SubscriptionEngineHandlers::kTest, [module_internal, this](auto &, const auto &msg) { @@ -90,7 +88,7 @@ namespace lean::loaders { on_block_response_ = se::SubscriberCreator< qtils::Empty, std::shared_ptr>:: - template create( + create( *se_manager_, SubscriptionEngineHandlers::kTest, [module_internal, this](auto &, const auto &msg) { @@ -101,13 +99,13 @@ namespace lean::loaders { } }); - se_manager_->notify(lean::EventTypes::SynchronizerIsLoaded); + se_manager_->notify(EventTypes::SynchronizerIsLoaded); } void dispatch_block_request( std::shared_ptr msg) override { SL_TRACE(logger_, "Dispatch BlockRequest; rid={}", msg->ctx.rid); - se_manager_->notify(lean::EventTypes::BlockRequest, msg); + se_manager_->notify(EventTypes::BlockRequest, msg); } }; diff --git a/src/loaders/loader.hpp b/src/loaders/loader.hpp index b9b73d9..bdfc923 100644 --- a/src/loaders/loader.hpp +++ b/src/loaders/loader.hpp @@ -15,6 +15,9 @@ #include "modules/module.hpp" #include "se/subscription_fwd.hpp" +namespace lean::blockchain { + class BlockTree; +} namespace lean::log { class LoggingSystem; } // namespace lean::log @@ -60,7 +63,7 @@ namespace lean::loaders { } void set_module(std::shared_ptr module) { - module_ = module; + module_ = std::move(module); } protected: diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 8aef530..47fc497 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -8,7 +8,7 @@ if(NOT TARGET all_modules) add_custom_target(all_modules) endif() -function(add_jam_module NAME) +function(add_lean_module NAME) set(MODULE_NAME ${NAME}) set(MODULE "${MODULE_NAME}_module") @@ -95,5 +95,8 @@ add_subdirectory(example) # Networking module add_subdirectory(networking) +# Block-production module +add_subdirectory(production) + # Blockchain synchronizer module add_subdirectory(synchronizer) diff --git a/src/modules/example/CMakeLists.txt b/src/modules/example/CMakeLists.txt index 7ff734f..3d48a5b 100644 --- a/src/modules/example/CMakeLists.txt +++ b/src/modules/example/CMakeLists.txt @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # -add_jam_module(example +add_lean_module(example SOURCE example.cpp INCLUDE_DIRS diff --git a/src/modules/example/example.hpp b/src/modules/example/example.hpp index 1e3a189..4c45710 100644 --- a/src/modules/example/example.hpp +++ b/src/modules/example/example.hpp @@ -8,18 +8,17 @@ #include #include +#include namespace lean::modules { class ExampleModuleImpl final : public lean::modules::ExampleModule { - lean::modules::ExampleModuleLoader &loader_; - qtils::SharedRef logsys_; - lean::log::Logger logger_; - - public: ExampleModuleImpl(lean::modules::ExampleModuleLoader &loader, qtils::SharedRef logsys); + public: + CREATE_SHARED_METHOD(ExampleModuleImpl); + void on_loaded_success() override; void on_loading_is_finished() override; @@ -29,6 +28,11 @@ namespace lean::modules { void on_response(std::shared_ptr s) override; void on_notify(std::shared_ptr s) override; + + private: + lean::modules::ExampleModuleLoader &loader_; + qtils::SharedRef logsys_; + lean::log::Logger logger_; }; diff --git a/src/modules/example/module.cpp b/src/modules/example/module.cpp index 11e8ac4..09ba110 100644 --- a/src/modules/example/module.cpp +++ b/src/modules/example/module.cpp @@ -26,7 +26,7 @@ MODULE_C_API std::weak_ptr query_module_instance( lean::modules::ExampleModuleLoader &loader, std::shared_ptr logger) { if (!module_instance) { - module_instance = std::make_shared( + module_instance = lean::modules::ExampleModuleImpl::create_shared( loader, std::move(logger)); } return module_instance; diff --git a/src/modules/networking/CMakeLists.txt b/src/modules/networking/CMakeLists.txt index b97451e..1d6cda2 100644 --- a/src/modules/networking/CMakeLists.txt +++ b/src/modules/networking/CMakeLists.txt @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # -add_jam_module(networking +add_lean_module(networking SOURCE networking.cpp INCLUDE_DIRS diff --git a/src/modules/networking/module.cpp b/src/modules/networking/module.cpp index 11be590..1e4be59 100644 --- a/src/modules/networking/module.cpp +++ b/src/modules/networking/module.cpp @@ -26,8 +26,8 @@ MODULE_C_API std::weak_ptr query_module_instance( lean::modules::NetworkingLoader &loader, std::shared_ptr logsys) { if (!module_instance) { - module_instance = std::make_shared( - loader, std::move(logsys)); + module_instance = + lean::modules::NetworkingImpl::create_shared(loader, std::move(logsys)); } return module_instance; } diff --git a/src/modules/networking/networking.hpp b/src/modules/networking/networking.hpp index 5576bb3..6a24984 100644 --- a/src/modules/networking/networking.hpp +++ b/src/modules/networking/networking.hpp @@ -15,13 +15,12 @@ namespace lean::modules { class NetworkingImpl final : public Singleton, public Networking { - public: - static std::shared_ptr instance; - CREATE_SHARED_METHOD(NetworkingImpl); - NetworkingImpl(NetworkingLoader &loader, qtils::SharedRef logging_system); + public: + CREATE_SHARED_METHOD(NetworkingImpl); + void on_loaded_success() override; void on_loading_is_finished() override; diff --git a/src/modules/production/CMakeLists.txt b/src/modules/production/CMakeLists.txt new file mode 100644 index 0000000..c92cfbb --- /dev/null +++ b/src/modules/production/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +add_lean_module(production + SOURCE + production.cpp + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/src + DEFINITIONS + SOME_FLAG=1 + LIBRARIES + qtils::qtils + soralog::soralog + sszpp +) \ No newline at end of file diff --git a/src/modules/production/interfaces.hpp b/src/modules/production/interfaces.hpp new file mode 100644 index 0000000..772cf18 --- /dev/null +++ b/src/modules/production/interfaces.hpp @@ -0,0 +1,42 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace lean::messages { + struct SlotStarted; + struct Finalized; + struct NewLeaf; +} // namespace lean::messages +namespace lean { + struct Block; +} + +namespace lean::modules { + + struct ProductionLoader { + virtual ~ProductionLoader() = default; + + virtual void dispatch_block_produced(std::shared_ptr) = 0; + }; + + struct ProductionModule { + virtual ~ProductionModule() = default; + virtual void on_loaded_success() = 0; + virtual void on_loading_is_finished() = 0; + + virtual void on_slot_started( + std::shared_ptr) = 0; + + virtual void on_leave_update(std::shared_ptr) = 0; + + virtual void on_block_finalized( + std::shared_ptr) = 0; + }; + +} // namespace lean::modules diff --git a/src/modules/production/module.cpp b/src/modules/production/module.cpp new file mode 100644 index 0000000..1f8ad49 --- /dev/null +++ b/src/modules/production/module.cpp @@ -0,0 +1,41 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define MODULE_C_API extern "C" __attribute__((visibility("default"))) +#define MODULE_API __attribute__((visibility("default"))) + +MODULE_C_API const char *loader_id() { + return "ProductionLoader"; +} + +MODULE_C_API const char *module_info() { + return "ProductionModule v1.0"; +} + +static std::shared_ptr module_instance; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage" + +MODULE_C_API std::weak_ptr +query_module_instance(lean::modules::ProductionLoader &loader, + std::shared_ptr logger, + std::shared_ptr block_tree, + qtils::SharedRef hasher) { + if (!module_instance) { + module_instance = lean::modules::ProductionModuleImpl::create_shared( + loader, std::move(logger), std::move(block_tree), std::move(hasher)); + } + return module_instance; +} + +MODULE_C_API void release_module_instance() { + module_instance.reset(); +} + +#pragma GCC diagnostic pop diff --git a/src/modules/production/production.cpp b/src/modules/production/production.cpp new file mode 100644 index 0000000..99fcab0 --- /dev/null +++ b/src/modules/production/production.cpp @@ -0,0 +1,103 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "modules/production/production.hpp" + +#include "blockchain/block_tree.hpp" +#include "crypto/hasher.hpp" +#include "modules/shared/prodution_types.tmp.hpp" +#include "types/block_data.hpp" +#include "types/signed_block.hpp" + +namespace lean::modules { + ProductionModuleImpl::ProductionModuleImpl( + ProductionLoader &loader, + qtils::SharedRef logging_system, + qtils::SharedRef block_tree, + qtils::SharedRef hasher) + : loader_(loader), + logsys_(std::move(logging_system)), + logger_(logsys_->getLogger("ProductionModule", "production_module")), + block_tree_(std::move(block_tree)), + hasher_(std::move(hasher)) {} + + void ProductionModuleImpl::on_loaded_success() { + SL_INFO(logger_, "Loaded success"); + } + + void ProductionModuleImpl::on_loading_is_finished() { + SL_INFO(logger_, "Loading is finished"); + } + + void ProductionModuleImpl::on_slot_started( + std::shared_ptr msg) { + if (msg->epoch_change) { + SL_INFO(logger_, "Epoch changed to {}", msg->epoch); + } + + auto is_producer = msg->slot % 3 == 2; // qdrvm validator indices for dev + + SL_INFO(logger_, + "Slot {} is started{}", + msg->slot, + is_producer ? " - I'm a producer" : ""); + + if (is_producer) { + auto parent_hash = block_tree_->bestBlock().hash; + // Produce block + BlockBody body; + + BlockHeader header; + header.slot = msg->slot; + header.proposer_index = 2; + header.parent_root = parent_hash; + header.state_root = {}; + header.body_root = {}; + header.updateHash(*hasher_); + + BlockData block_data; + block_data.hash = header.hash(); + block_data.header.emplace(header); + block_data.body.emplace(body); + block_data.signature = {}; + + Block block; + block.slot = msg->slot; + block.proposer_index = 2; + block.parent_root = parent_hash; + block.state_root = {}; + block.body = body; + + // Add a block into the block tree + auto res = block_tree_->addBlock(block); + if (res.has_error()) { + SL_ERROR( + logger_, "Could not add block to the block tree: {}", res.error()); + return; + } + + // Notify subscribers + loader_.dispatch_block_produced(std::make_shared(block)); + } + } + + void ProductionModuleImpl::on_leave_update( + std::shared_ptr msg) { + SL_INFO(logger_, + "New leaf {} appeared{}", + msg->header.index(), + msg->best ? "; it's the new the best leaf" : ""); + } + + void ProductionModuleImpl::on_block_finalized( + std::shared_ptr msg) { + SL_INFO(logger_, "Chain finalized on block {}", msg->finalized); + for (auto retired : msg->retired) { + SL_INFO(logger_, "Block {} is retired", retired); + } + } + +} // namespace lean::modules diff --git a/src/modules/production/production.hpp b/src/modules/production/production.hpp new file mode 100644 index 0000000..61d583c --- /dev/null +++ b/src/modules/production/production.hpp @@ -0,0 +1,49 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +namespace lean::crypto { + class Hasher; +} +namespace lean::blockchain { + class BlockTree; +} + +namespace lean::modules { + + class ProductionModuleImpl final : public lean::modules::ProductionModule { + ProductionModuleImpl(lean::modules::ProductionLoader &loader, + qtils::SharedRef logsys, + qtils::SharedRef block_tree, + qtils::SharedRef hasher); + + public: + CREATE_SHARED_METHOD(ProductionModuleImpl); + + void on_loaded_success() override; + void on_loading_is_finished() override; + + void on_slot_started(std::shared_ptr) override; + + void on_leave_update(std::shared_ptr) override; + void on_block_finalized( + std::shared_ptr) override; + + private: + lean::modules::ProductionLoader &loader_; + qtils::SharedRef logsys_; + lean::log::Logger logger_; + qtils::SharedRef block_tree_; + qtils::SharedRef hasher_; + }; + + +} // namespace lean::modules diff --git a/src/modules/shared/networking_types.tmp.hpp b/src/modules/shared/networking_types.tmp.hpp index 2226d77..d5c7f30 100644 --- a/src/modules/shared/networking_types.tmp.hpp +++ b/src/modules/shared/networking_types.tmp.hpp @@ -6,9 +6,9 @@ #pragma once -#include "lean_types/block.hpp" -#include "lean_types/block_header.hpp" -#include "lean_types/types.hpp" +#include "types/block.hpp" +#include "types/block_header.hpp" +#include "types/types.hpp" #include "utils/request_id.hpp" namespace lean::messages { @@ -36,7 +36,7 @@ namespace lean::messages { struct BlockRequestMessage { RequestCxt ctx; - BlocksRequest request; + // BlocksRequest request; PeerId peer; }; diff --git a/src/modules/shared/prodution_types.tmp.hpp b/src/modules/shared/prodution_types.tmp.hpp new file mode 100644 index 0000000..cee1800 --- /dev/null +++ b/src/modules/shared/prodution_types.tmp.hpp @@ -0,0 +1,30 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "types/block_header.hpp" +#include "types/types.hpp" + +namespace lean::messages { + + struct SlotStarted { + Slot slot; + Epoch epoch; + bool epoch_change; + }; + + struct NewLeaf { + BlockHeader header; + bool best = false; + }; + + struct Finalized { + BlockIndex finalized; + std::vector retired; + }; + +} // namespace lean::messages diff --git a/src/modules/shared/synchronizer_types.tmp.hpp b/src/modules/shared/synchronizer_types.tmp.hpp index a6590cb..5f72456 100644 --- a/src/modules/shared/synchronizer_types.tmp.hpp +++ b/src/modules/shared/synchronizer_types.tmp.hpp @@ -6,7 +6,7 @@ #pragma once -#include "lean_types/types.hpp" +#include "types/types.hpp" namespace lean::messages { diff --git a/src/modules/synchronizer/CMakeLists.txt b/src/modules/synchronizer/CMakeLists.txt index fe07bf0..187ea13 100644 --- a/src/modules/synchronizer/CMakeLists.txt +++ b/src/modules/synchronizer/CMakeLists.txt @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # -add_jam_module(synchronizer +add_lean_module(synchronizer SOURCE synchronizer.cpp INCLUDE_DIRS diff --git a/src/modules/synchronizer/module.cpp b/src/modules/synchronizer/module.cpp index 814282d..014d481 100644 --- a/src/modules/synchronizer/module.cpp +++ b/src/modules/synchronizer/module.cpp @@ -26,7 +26,7 @@ MODULE_C_API std::weak_ptr query_module_instance( lean::modules::SynchronizerLoader &loader, std::shared_ptr logsys) { if (!module_instance) { - module_instance = std::make_shared( + module_instance = lean::modules::SynchronizerImpl::create_shared( loader, std::move(logsys)); } return module_instance; diff --git a/src/modules/synchronizer/synchronizer.hpp b/src/modules/synchronizer/synchronizer.hpp index 50df250..a55a25e 100644 --- a/src/modules/synchronizer/synchronizer.hpp +++ b/src/modules/synchronizer/synchronizer.hpp @@ -16,12 +16,12 @@ namespace lean::modules { class SynchronizerImpl final : public Singleton, public Synchronizer { + SynchronizerImpl(SynchronizerLoader &loader, + qtils::SharedRef logging_system); + public: - static std::shared_ptr instance; CREATE_SHARED_METHOD(SynchronizerImpl); - SynchronizerImpl(SynchronizerLoader &loader, - qtils::SharedRef logging_system); void on_loaded_success() override; diff --git a/src/se/subscription.hpp b/src/se/subscription.hpp index 21bc56f..57bd972 100644 --- a/src/se/subscription.hpp +++ b/src/se/subscription.hpp @@ -8,31 +8,90 @@ #include +#include + #include "impl/common.hpp" #include "impl/subscriber_impl.hpp" #include "impl/subscription_manager.hpp" #include "subscription_fwd.hpp" +/** + * @file + * @brief Subscriber factory helper for the Subscription Engine. + * + * This header provides a tiny convenience layer to create and configure + * typed subscribers bound to a particular @ref Subscription instance. + * It wires a user callback and subscribes to a specific event key using the + * engine's dispatcher thread id. + */ + +/** + * Principles of operation + * ----------------------- + * - A @ref BaseSubscriber is created for the target @ref Subscription engine + * using the provided @c ContextType and @c EventData... types. + * - A user callback is installed; it will be invoked with a reference to the + * context object and the event payload (expanded from @c EventData...). + * - The subscriber is subscribed to a concrete @ref EventTypes key using the + * supplied dispatcher thread identifier (@ref Dispatcher::Tid). + * - Event routing asserts that the runtime key equals the compile‑time key + * specified at the call site of @c create. + */ + namespace lean::se { + /** + * @brief Obtain a shared dispatcher instance. + * + * @return Shared pointer to a @ref Dispatcher used by the Subscription + * Engine to route events. + */ std::shared_ptr getDispatcher(); - template + /** + * @tparam ContextType Type of the per-subscriber context object passed to + * callbacks. Can be set into `qtils::Empty` if it is + * unnecessary + * @tparam EventData Variadic list of event payload types delivered to the + * callback in the declared order. + */ + template struct SubscriberCreator { + /** + * @brief Create and subscribe a typed subscriber with a user callback. + * + * Creates a @ref BaseSubscriber bound to the engine taken from the given + * @ref Subscription, installs the callback, and subscribes it to the + * specified event @p key on the provided dispatcher thread id @p tid. + * + * @tparam key Compile‑time event key (an enumerator of @ref EventTypes) + * this subscriber should receive. + * @tparam F Callable type of the user callback. The callback must be + * invocable with arguments: (ContextType&, EventData...). + * @tparam Args Additional arguments forwarded to the underlying + * @c BaseSubscriber::create factory (e.g., context args). + * + * @param se Reference to the owning @ref Subscription. + * @param tid Dispatcher thread identifier to bind the subscription to. + * @param callback User callback invoked for delivered events. + * @param args Extra arguments forwarded to subscriber construction. + * + * @return A @c std::shared_ptr to the created @ref BaseSubscriber. + */ template static auto create(Subscription &se, SubscriptionEngineHandlers tid, F &&callback, Args &&...args) { - auto subscriber = BaseSubscriber::create( + auto subscriber = BaseSubscriber::create( se.getEngine(), std::forward(args)...); subscriber->setCallback( [f{std::forward(callback)}](auto /*set_id*/, - auto &object, + auto &context, auto event_key, EventData... args) mutable { assert(key == event_key); - std::forward(f)(object, std::move(args)...); + std::forward(f)(context, std::move(args)...); }); subscriber->subscribe(0, key, static_cast(tid)); return subscriber; diff --git a/src/se/subscription_fwd.hpp b/src/se/subscription_fwd.hpp index abe96cf..0edae2e 100644 --- a/src/se/subscription_fwd.hpp +++ b/src/se/subscription_fwd.hpp @@ -6,7 +6,6 @@ #pragma once -#include #include namespace lean { @@ -48,11 +47,20 @@ namespace lean { PeerConnected, /// Peer disconnected PeerDisconnected, - /// Data of block is requested + /// Data of a block is requested BlockRequest, - /// Data of block is respond + /// Data of a block is respond BlockResponse, + // -- Block production + + /// Production module is loaded + ProductionIsLoaded, + /// Production module is unloaded + ProductionIsUnloaded, + /// New block produced + BlockProduced, + // -- Synchronizer /// Synchronizer module is loaded @@ -63,6 +71,18 @@ namespace lean { BlockAnnounceReceived, /// New block index discovered BlockIndexDiscovered, + + // -- Block tree + + /// New leaf + BlockAdded, + /// Finalized + BlockFinalized, + + // -- Timeline + + /// New slot started + SlotStarted, }; static constexpr uint32_t kThreadPoolSize = 3u; @@ -82,8 +102,8 @@ namespace lean { using Dispatcher = se::Dispatcher; using Subscription = se::SubscriptionManager; - template + template using BaseSubscriber = - se::SubscriberImpl; + se::SubscriberImpl; } // namespace lean diff --git a/src/storage/rocksdb/rocksdb.cpp b/src/storage/rocksdb/rocksdb.cpp index fd3e2f8..37965b5 100644 --- a/src/storage/rocksdb/rocksdb.cpp +++ b/src/storage/rocksdb/rocksdb.cpp @@ -178,10 +178,8 @@ namespace lean::storage { options.create_missing_column_families = true; - if (no_db_presented) { - qtils::raise_on_err(openDatabaseWithTTL( - options, path, column_family_descriptors, ttls, *this, logger_)); - } + qtils::raise_on_err(openDatabaseWithTTL( + options, path, column_family_descriptors, ttls, *this, logger_)); // Print size of each column family SL_VERBOSE(logger_, "Current column family sizes:"); diff --git a/src/storage/spaces.hpp b/src/storage/spaces.hpp index 4eba0f9..6e97cd6 100644 --- a/src/storage/spaces.hpp +++ b/src/storage/spaces.hpp @@ -28,8 +28,8 @@ namespace lean::storage { * specific BufferStorage instances. */ enum class Space : uint8_t { - Default = 0, ///< Default space used for general-purpose storage - LookupKey, ///< Space used for mapping lookup keys + Default = 0, ///< Default space used for general-purpose storage + SlotToHashes, ///< Space used for mapping lookup keys // application-defined spaces Header, diff --git a/src/lean_types/block.hpp b/src/types/block.hpp similarity index 90% rename from src/lean_types/block.hpp rename to src/types/block.hpp index 2bbdc68..d9b689d 100644 --- a/src/lean_types/block.hpp +++ b/src/types/block.hpp @@ -8,7 +8,7 @@ #include -#include "lean_types/block_body.hpp" +#include "types/block_body.hpp" namespace lean { diff --git a/src/lean_types/block_body.hpp b/src/types/block_body.hpp similarity index 84% rename from src/lean_types/block_body.hpp rename to src/types/block_body.hpp index abae44b..8eb898e 100644 --- a/src/lean_types/block_body.hpp +++ b/src/types/block_body.hpp @@ -8,8 +8,8 @@ #include -#include "lean_types/constants.hpp" -#include "lean_types/vote.hpp" +#include "types/constants.hpp" +#include "types/vote.hpp" namespace lean { diff --git a/src/lean_types/block_data.hpp b/src/types/block_data.hpp similarity index 100% rename from src/lean_types/block_data.hpp rename to src/types/block_data.hpp diff --git a/src/lean_types/block_header.hpp b/src/types/block_header.hpp similarity index 93% rename from src/lean_types/block_header.hpp rename to src/types/block_header.hpp index c2b05f9..c1fe152 100644 --- a/src/lean_types/block_header.hpp +++ b/src/types/block_header.hpp @@ -7,11 +7,12 @@ #pragma once #include -#include #include +#include #include #include "serde/serialization.hpp" +#include "types/block_index.hpp" namespace lean { @@ -53,7 +54,7 @@ namespace lean { auto enc_res = encode(*this); BOOST_ASSERT_MSG(enc_res.has_value(), "Header should be encoded errorless"); - hash_opt.emplace(hasher.blake2b_256(enc_res.value())); + hash_opt.emplace(hasher.sha2_256(enc_res.value())); } BlockIndex index() const { diff --git a/src/types/block_index.hpp b/src/types/block_index.hpp new file mode 100644 index 0000000..f3e1958 --- /dev/null +++ b/src/types/block_index.hpp @@ -0,0 +1,65 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "types/types.hpp" + +namespace lean { + + struct BlockIndex { + Slot slot; + BlockHash hash; + auto operator<=>(const BlockIndex &other) const = default; + }; + +} // namespace lean + +template <> +struct std::hash { + std::size_t operator()(const lean::BlockIndex &s) const noexcept { + std::size_t h = std::hash{}(s.slot); + for (auto b : s.hash) { + h ^= std::hash{}(b) + 0x9e3779b97f4a7c15ULL + (h << 6) + + (h >> 2); + } + return h; + } +}; + +template <> +struct fmt::formatter { + // Presentation format + bool long_form = false; + + // Parses format specifications of the form ['s' | 'l']. + constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it != end) { + if (*it == 'l' or *it == 's') { + long_form = *it == 'l'; + ++it; + } + } + if (it != end && *it != '}') { + throw format_error("invalid format"); + } + return it; + } + + // Formats the BlockIndex using the parsed format specification (presentation) + // stored in this formatter. + template + auto format(const lean::BlockIndex &block_index, FormatContext &ctx) const + -> decltype(ctx.out()) { + [[unlikely]] if (long_form) { + return fmt::format_to( + ctx.out(), "{:0xx} @ {}", block_index.hash, block_index.slot); + } + return fmt::format_to( + ctx.out(), "{:0x} @ {}", block_index.hash, block_index.slot); + } +}; diff --git a/src/lean_types/block_signature.hpp b/src/types/block_signature.hpp similarity index 100% rename from src/lean_types/block_signature.hpp rename to src/types/block_signature.hpp diff --git a/src/lean_types/chekpoint.hpp b/src/types/chekpoint.hpp similarity index 100% rename from src/lean_types/chekpoint.hpp rename to src/types/chekpoint.hpp diff --git a/src/lean_types/config.hpp b/src/types/config.hpp similarity index 100% rename from src/lean_types/config.hpp rename to src/types/config.hpp diff --git a/src/lean_types/constants.hpp b/src/types/constants.hpp similarity index 100% rename from src/lean_types/constants.hpp rename to src/types/constants.hpp diff --git a/src/lean_types/justification.hpp b/src/types/justification.hpp similarity index 89% rename from src/lean_types/justification.hpp rename to src/types/justification.hpp index fddeb9e..bc27d4c 100644 --- a/src/lean_types/justification.hpp +++ b/src/types/justification.hpp @@ -6,9 +6,9 @@ #pragma once -#include #include #include +#include namespace lean { diff --git a/src/lean_types/signed_block.hpp b/src/types/signed_block.hpp similarity index 79% rename from src/lean_types/signed_block.hpp rename to src/types/signed_block.hpp index c1057ec..cd7024c 100644 --- a/src/lean_types/signed_block.hpp +++ b/src/types/signed_block.hpp @@ -6,6 +6,10 @@ #pragma once +#include + +#include "types/block.hpp" + namespace lean { struct SignedBlock { diff --git a/src/lean_types/signed_vote.hpp b/src/types/signed_vote.hpp similarity index 100% rename from src/lean_types/signed_vote.hpp rename to src/types/signed_vote.hpp diff --git a/src/lean_types/state.hpp b/src/types/state.hpp similarity index 100% rename from src/lean_types/state.hpp rename to src/types/state.hpp diff --git a/src/types/types.hpp b/src/types/types.hpp new file mode 100644 index 0000000..5d7e670 --- /dev/null +++ b/src/types/types.hpp @@ -0,0 +1,82 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +namespace lean { + // stub types. must be refactored in future + + struct Stub {}; + + // blockchain types + + using OpaqueHash = qtils::ByteArr<32>; + + using BlockHash = OpaqueHash; + using HeaderHash = OpaqueHash; + using StateRoot = OpaqueHash; + using BodyRoot = OpaqueHash; + + using Slot = uint64_t; + using Epoch = uint64_t; // is needed? + + using ProposerIndex = uint64_t; + + // networking types + + using PeerId = qtils::Tagged; // STUB + + // /// Direction, in which to retrieve ordered data + // enum class Direction : uint8_t { + // /// from child to parent + // ASCENDING = 0, + // /// from parent to canonical child + // DESCENDING = 1 + // }; + // + // /// Request for blocks to another peer + // struct BlocksRequest { + // /// start from this block + // BlockIndex from{}; + // /// sequence direction + // Direction direction{}; + // /// maximum number of blocks to return; an implementation defined maximum + // is + // /// used when unspecified + // std::optional max{}; + // bool multiple_justifications = true; + // }; + // + // struct BlockAnnounce { + // BlockAnnounce(const BlockAnnounce &) = delete; + // }; + +} // namespace lean + +template <> +struct fmt::formatter { + constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it != '}') { + throw format_error("invalid format"); + } + return it; + } + + template + auto format(const lean::Stub &, FormatContext &ctx) const + -> decltype(ctx.out()) { + return fmt::format_to(ctx.out(), "stub"); + } +}; + +template +struct fmt::formatter> : formatter {}; diff --git a/src/lean_types/vote.hpp b/src/types/vote.hpp similarity index 91% rename from src/lean_types/vote.hpp rename to src/types/vote.hpp index 4d7dfa3..03662f9 100644 --- a/src/lean_types/vote.hpp +++ b/src/types/vote.hpp @@ -6,7 +6,7 @@ #pragma once -#include +#include namespace lean { diff --git a/tests/mock/crypto/hasher_mock.hpp b/tests/mock/crypto/hasher_mock.hpp index 082f342..a3a1ba3 100644 --- a/tests/mock/crypto/hasher_mock.hpp +++ b/tests/mock/crypto/hasher_mock.hpp @@ -18,25 +18,25 @@ namespace lean::crypto { public: ~HasherMock() override = default; - MOCK_METHOD(Hash64, twox_64, (qtils::ByteView), (const, override)); - - MOCK_METHOD(Hash64, blake2b_64, (qtils::ByteView), (const, override)); - - MOCK_METHOD(Hash128, blake2b_128, (qtils::ByteView), (const, override)); - - MOCK_METHOD(Hash128, twox_128, (qtils::ByteView), (const, override)); - - MOCK_METHOD(Hash256, twox_256, (qtils::ByteView), (const, override)); - - MOCK_METHOD(Hash256, blake2b_256, (qtils::ByteView), (const, override)); - - MOCK_METHOD(Hash256, blake2s_256, (qtils::ByteView), (const, override)); - - MOCK_METHOD(Hash256, keccak_256, (qtils::ByteView), (const, override)); + // MOCK_METHOD(Hash64, twox_64, (qtils::ByteView), (const, override)); + // + // MOCK_METHOD(Hash64, blake2b_64, (qtils::ByteView), (const, override)); + // + // MOCK_METHOD(Hash128, blake2b_128, (qtils::ByteView), (const, override)); + // + // MOCK_METHOD(Hash128, twox_128, (qtils::ByteView), (const, override)); + // + // MOCK_METHOD(Hash256, twox_256, (qtils::ByteView), (const, override)); + // + // MOCK_METHOD(Hash256, blake2b_256, (qtils::ByteView), (const, override)); + // + // MOCK_METHOD(Hash256, blake2s_256, (qtils::ByteView), (const, override)); + // + // MOCK_METHOD(Hash256, keccak_256, (qtils::ByteView), (const, override)); MOCK_METHOD(Hash256, sha2_256, (qtils::ByteView), (const, override)); - MOCK_METHOD(Hash512, blake2b_512, (qtils::ByteView), (const, override)); + // MOCK_METHOD(Hash512, blake2b_512, (qtils::ByteView), (const, override)); }; } // namespace lean::crypto diff --git a/tests/unit/blockchain/block_storage_test.cpp b/tests/unit/blockchain/block_storage_test.cpp index e587337..b97d91a 100644 --- a/tests/unit/blockchain/block_storage_test.cpp +++ b/tests/unit/blockchain/block_storage_test.cpp @@ -6,6 +6,7 @@ #include +#include #include #include @@ -16,23 +17,18 @@ #include "mock/crypto/hasher_mock.hpp" #include "mock/storage/generic_storage_mock.hpp" #include "mock/storage/spaced_storage_mock.hpp" +#include "qtils/error_throw.hpp" #include "sszpp/ssz++.hpp" #include "storage/storage_error.hpp" -// #include "testutil/literals.hpp" -#include -#include - -#include "lean_types/block_data.hpp" -#include "qtils/error_throw.hpp" #include "testutil/literals.hpp" #include "testutil/prepare_loggers.hpp" +#include "types/block_data.hpp" using lean::Block; using lean::BlockBody; using lean::BlockData; using lean::BlockHash; using lean::BlockHeader; -using lean::BlockNumber; using lean::encode; using lean::app::ChainSpecMock; using lean::blockchain::BlockStorageError; @@ -65,7 +61,7 @@ class BlockStorageTest : public testing::Test { Space::Header, Space::Justification, Space::Body, - Space::LookupKey}; + Space::SlotToHashes}; for (auto space : required_spaces) { auto storage = std::make_shared(); @@ -98,7 +94,7 @@ class BlockStorageTest : public testing::Test { qtils::SharedRef createWithGenesis() { // calculate hash of genesis block at put block header static auto encoded_header = ByteVec(encode(BlockHeader{}).value()); - ON_CALL(*hasher, blake2b_256(encoded_header.view())) + ON_CALL(*hasher, sha2_256(encoded_header.view())) .WillByDefault(Return(genesis_block_hash)); auto new_block_storage = std::make_shared( @@ -146,7 +142,7 @@ TEST_F(BlockStorageTest, CreateWithEmptyStorage) { */ TEST_F(BlockStorageTest, CreateWithExistingGenesis) { // trying to get header of genesis block - EXPECT_CALL(*(spaces[Space::Header]), contains(ByteView{genesis_block_hash})) + EXPECT_CALL(*spaces[Space::Header], contains(ByteView{genesis_block_hash})) .WillOnce(Return(outcome::success(true))); // Init underlying storage @@ -165,7 +161,7 @@ TEST_F(BlockStorageTest, CreateWithExistingGenesis) { */ TEST_F(BlockStorageTest, CreateWithStorageError) { // trying to get header of genesis block - EXPECT_CALL(*(spaces[Space::Header]), contains(ByteView{genesis_block_hash})) + EXPECT_CALL(*spaces[Space::Header], contains(ByteView{genesis_block_hash})) .WillOnce(Return(lean::storage::StorageError::IO_ERROR)); // Init underlying storage @@ -230,18 +226,27 @@ TEST_F(BlockStorageTest, TryGetBlockNotFound) { TEST_F(BlockStorageTest, PutWithStorageError) { auto block_storage = createWithGenesis(); + BlockBody body; + + BlockHeader header; + header.slot = 1; + header.parent_root = genesis_block_hash; + header.body_root = {};//ssz::hash_tree_root(body); + header.updateHash(*hasher); + BlockData block; - block.header.emplace(); + block.header.emplace(header); block.header->slot = 1; block.header->parent_root = genesis_block_hash; + block.body.emplace(body); auto encoded_header = ByteVec(encode(*block.header).value()); - ON_CALL(*hasher, blake2b_256(encoded_header.view())) + ON_CALL(*hasher, sha2_256(encoded_header.view())) .WillByDefault(Return(regular_block_hash)); ByteVec key{regular_block_hash}; - EXPECT_CALL(*(spaces[Space::Body]), put(key.view(), _)) + EXPECT_CALL(*spaces[Space::Body], put(key.view(), _)) .WillOnce(Return(lean::storage::StorageError::IO_ERROR)); ASSERT_OUTCOME_ERROR(block_storage->putBlock(block), @@ -261,18 +266,18 @@ TEST_F(BlockStorageTest, Remove) { ByteVec encoded_header{encode(BlockHeader{}).value()}; - EXPECT_CALL(*(spaces[Space::Header]), tryGetMock(hash)) + EXPECT_CALL(*spaces[Space::Header], tryGetMock(hash)) .WillOnce(Return(encoded_header)); - EXPECT_CALL(*(spaces[Space::Body]), remove(hash)) + EXPECT_CALL(*spaces[Space::Body], remove(hash)) .WillOnce(Return(outcome::success())); - EXPECT_CALL(*(spaces[Space::Header]), remove(hash)) + EXPECT_CALL(*spaces[Space::Header], remove(hash)) .WillOnce(Return(outcome::success())); - EXPECT_CALL(*(spaces[Space::Justification]), remove(hash)) + EXPECT_CALL(*spaces[Space::Justification], remove(hash)) .WillOnce(Return(outcome::success())); ASSERT_OUTCOME_SUCCESS(block_storage->removeBlock(genesis_block_hash)); - EXPECT_CALL(*(spaces[Space::Header]), tryGetMock(hash)) + EXPECT_CALL(*spaces[Space::Header], tryGetMock(hash)) .WillOnce(Return(std::nullopt)); ASSERT_OUTCOME_SUCCESS(block_storage->removeBlock(genesis_block_hash));