diff --git a/CMakeLists.txt b/CMakeLists.txt index dd20857fd8..c00e8c92ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,7 @@ if("${CMAKE_CXX_COMPILER_ID}" MATCHES "^(AppleClang|Clang|GNU)$") add_flag(-Werror=sign-compare) # warn the user if they compare a signed and unsigned numbers add_flag(-Werror=reorder) # field '$1' will be initialized after field '$2' add_flag(-Werror=mismatched-tags) # warning: class '$1' was previously declared as struct + add_flag(-Werror=switch) # unhandled values in a switch statement # cmake-format: on else() # promote to errors @@ -104,6 +105,7 @@ if("${CMAKE_CXX_COMPILER_ID}" MATCHES "^(AppleClang|Clang|GNU)$") add_flag(-Werror-non-virtual-dtor) # warn the user if a class with virtual functions has a non-virtual destructor. This helps catch hard to track down memory errors add_flag(-Werror-sign-compare) # warn the user if they compare a signed and unsigned numbers add_flag(-Werror-reorder) # field '$1' will be initialized after field '$2' + add_flag(-Werror-switch) # unhandled values in a switch statement # cmake-format: on endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") @@ -112,6 +114,9 @@ elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md#msvc endif() +print("C flags: ${CMAKE_C_FLAGS}") +print("CXX flags: ${CMAKE_CXX_FLAGS}") + if(COVERAGE) include(cmake/coverage.cmake) endif() @@ -164,6 +169,7 @@ kagome_install_setup( core/scale core/storage core/subscription + core/telemetry core/utils) include(CMakePackageConfigHelpers) diff --git a/core/api/service/child_state/impl/child_state_api_impl.cpp b/core/api/service/child_state/impl/child_state_api_impl.cpp index 10c1ace9e9..c66407e04d 100644 --- a/core/api/service/child_state/impl/child_state_api_impl.cpp +++ b/core/api/service/child_state/impl/child_state_api_impl.cpp @@ -129,7 +129,8 @@ namespace kagome::api { storage_->getEphemeralBatchAt(child_root_hash)); auto res = child_storage_trie_reader->tryGet(key); return common::map_result_optional( - std::move(res), [](common::BufferOrView &&r) { return r.into(); }); + std::move(res), + [](common::BufferOrView &&r) { return r.intoBuffer(); }); } outcome::result> diff --git a/core/api/service/state/impl/state_api_impl.cpp b/core/api/service/state/impl/state_api_impl.cpp index 02d65951e7..c2f8098feb 100644 --- a/core/api/service/state/impl/state_api_impl.cpp +++ b/core/api/service/state/impl/state_api_impl.cpp @@ -122,7 +122,7 @@ namespace kagome::api { OUTCOME_TRY(trie_reader, storage_->getEphemeralBatchAt(header.state_root)); auto res = trie_reader->tryGet(key); return common::map_result_optional( - std::move(res), [](common::BufferOrView &&r) { return r.into(); }); + std::move(res), [](common::BufferOrView &&r) { return r.intoBuffer(); }); } outcome::result> @@ -164,7 +164,7 @@ namespace kagome::api { OUTCOME_TRY(opt_get, batch->tryGet(key)); auto opt_value = common::map_optional( std::move(opt_get), - [](common::BufferOrView &&r) { return r.into(); }); + [](common::BufferOrView &&r) { return r.intoBuffer(); }); auto it = last_values.find(key); if (it == last_values.end() || it->second != opt_value) { change.changes.push_back(StorageChangeSet::Change{key, opt_value}); diff --git a/core/application/CMakeLists.txt b/core/application/CMakeLists.txt index b489ab1577..3b2d37fc48 100644 --- a/core/application/CMakeLists.txt +++ b/core/application/CMakeLists.txt @@ -37,6 +37,7 @@ target_link_libraries(app_config chain_spec build_version ) +kagome_install(app_config) add_library(chain_spec impl/chain_spec_impl.cpp diff --git a/core/application/app_configuration.hpp b/core/application/app_configuration.hpp index 65f28a1c80..39293d3629 100644 --- a/core/application/app_configuration.hpp +++ b/core/application/app_configuration.hpp @@ -252,6 +252,8 @@ namespace kagome::application { */ virtual StorageBackend storageBackend() const = 0; + virtual std::optional statePruningDepth() const = 0; + /** * @return database state cache size in MiB */ diff --git a/core/application/impl/app_configuration_impl.cpp b/core/application/impl/app_configuration_impl.cpp index 819dae4791..6e11431336 100644 --- a/core/application/impl/app_configuration_impl.cpp +++ b/core/application/impl/app_configuration_impl.cpp @@ -5,7 +5,6 @@ #include "application/impl/app_configuration_impl.hpp" -#include #include #include #include @@ -18,7 +17,6 @@ #include #include #include -#include "filesystem/common.hpp" #include "api/transport/tuner.hpp" #include "application/build_version.hpp" @@ -27,6 +25,7 @@ #include "chain_spec_impl.hpp" #include "common/hexutil.hpp" #include "common/uri.hpp" +#include "filesystem/common.hpp" #include "filesystem/directories.hpp" #include "utils/read_file.hpp" @@ -248,7 +247,8 @@ namespace kagome::application { offchain_worker_mode_{def_offchain_worker_mode}, enable_offchain_indexing_{def_enable_offchain_indexing}, recovery_state_{def_block_to_recover}, - db_cache_size_{def_db_cache_size} { + db_cache_size_{def_db_cache_size}, + state_pruning_depth_{} { SL_INFO(logger_, "Soramitsu Kagome started. Version: {} ", buildVersion()); } @@ -790,6 +790,7 @@ namespace kagome::application { ("db-cache", po::value()->default_value(def_db_cache_size), "Limit the memory the database cache can use ") ("enable-offchain-indexing", po::value(), "enable Offchain Indexing API, which allow block import to write to offchain DB)") ("recovery", po::value(), "recovers block storage to state after provided block presented by number or hash, and stop after that") + ("state-pruning", po::value()->default_value("archive"), "state pruning policy. 'archive' or the number of finalized blocks to keep.") ; po::options_description network_desc("Network options"); @@ -1445,6 +1446,27 @@ namespace kagome::application { return false; } + if (auto state_pruning_opt = + find_argument(vm, "state-pruning"); + state_pruning_opt.has_value()) { + const auto& val = state_pruning_opt.value(); + if (val == "archive") { + state_pruning_depth_ = std::nullopt; + } else { + uint32_t depth{}; + auto [_, err] = std::from_chars(&*val.begin(), &*val.end(), depth); + if (err == std::errc{}) { + state_pruning_depth_ = depth; + } else { + SL_ERROR(logger_, + "Failed to parse state-pruning param (which should be " + "either 'archive' or an integer): {}", + err); + return false; + } + } + } + // if something wrong with config print help message if (not validate_config()) { std::cout << desc << std::endl; diff --git a/core/application/impl/app_configuration_impl.hpp b/core/application/impl/app_configuration_impl.hpp index d7b3868944..0fcefe000a 100644 --- a/core/application/impl/app_configuration_impl.hpp +++ b/core/application/impl/app_configuration_impl.hpp @@ -184,6 +184,9 @@ namespace kagome::application { uint32_t dbCacheSize() const override { return db_cache_size_; } + std::optional statePruningDepth() const override { + return state_pruning_depth_; + } std::optional devMnemonicPhrase() const override { if (dev_mnemonic_phrase_) { return *dev_mnemonic_phrase_; @@ -337,6 +340,7 @@ namespace kagome::application { std::optional recovery_state_; StorageBackend storage_backend_ = StorageBackend::RocksDB; uint32_t db_cache_size_; + std::optional state_pruning_depth_; std::optional dev_mnemonic_phrase_; std::string node_wss_pem_; std::optional benchmark_config_; diff --git a/core/application/impl/chain_spec_impl.cpp b/core/application/impl/chain_spec_impl.cpp index 754935d3c4..c2f5e2f501 100644 --- a/core/application/impl/chain_spec_impl.cpp +++ b/core/application/impl/chain_spec_impl.cpp @@ -254,10 +254,10 @@ namespace kagome::application { auto read_key_block = [](const auto &tree, GenesisRawData &data) -> outcome::result { - for (const auto &[child_key, child_value] : tree) { + for (const auto &[key, value] : tree) { // get rid of leading 0x for key and value and unhex - OUTCOME_TRY(key_processed, common::unhexWith0x(child_key)); - OUTCOME_TRY(value_processed, common::unhexWith0x(child_value.data())); + OUTCOME_TRY(key_processed, common::unhexWith0x(key)); + OUTCOME_TRY(value_processed, common::unhexWith0x(value.data())); data.emplace_back(std::move(key_processed), std::move(value_processed)); } return outcome::success(); diff --git a/core/authority_discovery/query/query_impl.cpp b/core/authority_discovery/query/query_impl.cpp index 4dedd55838..661466fa5b 100644 --- a/core/authority_discovery/query/query_impl.cpp +++ b/core/authority_discovery/query/query_impl.cpp @@ -122,7 +122,7 @@ namespace kagome::authority_discovery { auto authority = queue_.back(); queue_.pop_back(); - common::Buffer hash = crypto::sha256(authority); + common::Buffer hash{crypto::sha256(authority)}; scheduler_->schedule([=, wp = weak_from_this()] { if (auto self = wp.lock()) { std::ignore = kademlia_->getValue( @@ -131,12 +131,12 @@ namespace kagome::authority_discovery { --active_; pop(); if (res.has_error()) { - SL_WARN(log_, "Kademlia can't get value: {}", res.error()); + SL_DEBUG(log_, "Kademlia can't get value: {}", res.error()); return; } auto r = add(authority, std::move(res.value())); if (not r) { - SL_WARN(log_, "Can't add: {}", r.error()); + SL_DEBUG(log_, "Can't add: {}", r.error()); } }); } diff --git a/core/benchmark/block_execution_benchmark.cpp b/core/benchmark/block_execution_benchmark.cpp index 41a52b5498..b36f34de2d 100644 --- a/core/benchmark/block_execution_benchmark.cpp +++ b/core/benchmark/block_execution_benchmark.cpp @@ -20,6 +20,8 @@ OUTCOME_CPP_DEFINE_CATEGORY(kagome::benchmark, using E = kagome::benchmark::BlockExecutionBenchmark::Error; case E::BLOCK_WEIGHT_DECODE_FAILED: return "Failed to decode block weight"; + case E::BLOCK_NOT_FOUND: + return "A block expected to be present in the block tree is not found"; } return "Unknown BlockExecutionBenchmark error"; } @@ -221,8 +223,12 @@ namespace kagome::benchmark { block_tree_->getBlockHash(config.start), "retrieving hash of block {}", config.start); + if (!current_hash) { + SL_ERROR(logger_, "Start block {} is not found!", config.start); + return Error::BLOCK_NOT_FOUND; + } - primitives::BlockInfo current_block_info = {config.start, current_hash}; + primitives::BlockInfo current_block_info = {config.start, *current_hash}; std::vector block_hashes; std::vector blocks; while (current_block_info.number <= config.end) { @@ -244,7 +250,14 @@ namespace kagome::benchmark { "retrieving hash of block {}", current_block_info.number + 1); current_block_info.number += 1; - current_block_info.hash = next_hash; + + if (!next_hash) { + SL_ERROR(logger_, + "Next block {} is not found!", + current_block_info.number + 1); + return Error::BLOCK_NOT_FOUND; + } + current_block_info.hash = *next_hash; } std::chrono::steady_clock clock; @@ -291,7 +304,8 @@ namespace kagome::benchmark { *trie_storage_, blocks[stat.getBlock().number - config.start].header.state_root)); fmt::print( - "Block #{}: consumed {} ns out of declared {} ns on average. ({} %)\n", + "Block #{}: consumed {} ns out of declared {} ns on average. ({} " + "%)\n", stat.getBlock().number, stat.avg().count(), block_weight_ns.count(), diff --git a/core/benchmark/block_execution_benchmark.hpp b/core/benchmark/block_execution_benchmark.hpp index 1776a0c24d..d083e5a140 100644 --- a/core/benchmark/block_execution_benchmark.hpp +++ b/core/benchmark/block_execution_benchmark.hpp @@ -32,6 +32,7 @@ namespace kagome::benchmark { public: enum class Error { BLOCK_WEIGHT_DECODE_FAILED, + BLOCK_NOT_FOUND, }; struct Config { diff --git a/core/blockchain/block_tree.hpp b/core/blockchain/block_tree.hpp index 91ce2c2485..4b86d48145 100644 --- a/core/blockchain/block_tree.hpp +++ b/core/blockchain/block_tree.hpp @@ -43,7 +43,7 @@ namespace kagome::blockchain { * @param block_number of the block header we are looking for * @return result containing block hash if it exists, error otherwise */ - virtual outcome::result getBlockHash( + virtual outcome::result> getBlockHash( primitives::BlockNumber block_number) const = 0; /** diff --git a/core/blockchain/impl/block_tree_impl.cpp b/core/blockchain/impl/block_tree_impl.cpp index 8cd38bfd9e..1362507c99 100644 --- a/core/blockchain/impl/block_tree_impl.cpp +++ b/core/blockchain/impl/block_tree_impl.cpp @@ -17,6 +17,8 @@ #include "crypto/blake2/blake2b.h" #include "log/profiling_logger.hpp" #include "storage/database_error.hpp" +#include "storage/trie_pruner/recover_pruner_state.hpp" +#include "storage/trie_pruner/trie_pruner.hpp" namespace { constexpr auto blockHeightMetricName = "kagome_block_height"; @@ -105,6 +107,9 @@ namespace kagome::blockchain { } } // namespace + BlockTreeImpl::SafeBlockTreeData::SafeBlockTreeData(BlockTreeData data) + : block_tree_data_{std::move(data)} {} + outcome::result> BlockTreeImpl::create( std::shared_ptr header_repo, std::shared_ptr storage, @@ -117,6 +122,7 @@ namespace kagome::blockchain { extrinsic_event_key_repo, std::shared_ptr justification_storage_policy, + std::shared_ptr state_pruner, std::shared_ptr<::boost::asio::io_context> io_context) { BOOST_ASSERT(storage != nullptr); BOOST_ASSERT(header_repo != nullptr); @@ -178,12 +184,22 @@ namespace kagome::blockchain { SL_WARN(log, "Can't get header of existing block {}: " "not found in block storage", - hash, - header_res.error()); + hash); break; } const auto &header = header_opt.value(); + if (header.number < last_finalized_block_info.number) { + SL_WARN( + log, + "Detected a leaf #{}({}) lower than the last finalized block " + "#{}", + header.number, + hash, + last_finalized_block_info.number); + break; + } + primitives::BlockInfo block(header.number, hash); collected.emplace(block, header); @@ -212,6 +228,7 @@ namespace kagome::blockchain { std::move(extrinsic_events_engine), std::move(extrinsic_event_key_repo), std::move(justification_storage_policy), + state_pruner, std::move(io_context))); // Add non-finalized block to the block tree @@ -230,6 +247,9 @@ namespace kagome::blockchain { log, "Existing non-finalized block {} is added to block tree", block); } + OUTCOME_TRY( + storage::trie_pruner::recoverPrunerState(*state_pruner, *block_tree)); + return block_tree; } @@ -344,10 +364,12 @@ namespace kagome::blockchain { extrinsic_event_key_repo, std::shared_ptr justification_storage_policy, + std::shared_ptr state_pruner, std::shared_ptr<::boost::asio::io_context> io_context) : block_tree_data_{BlockTreeData{ .header_repo_ = std::move(header_repo), .storage_ = std::move(storage), + .state_pruner_ = std::move(state_pruner), .tree_ = std::move(cached_tree), .extrinsic_observer_ = std::move(extrinsic_observer), .hasher_ = std::move(hasher), @@ -364,6 +386,7 @@ namespace kagome::blockchain { BOOST_ASSERT(p.hasher_ != nullptr); BOOST_ASSERT(p.extrinsic_event_key_repo_ != nullptr); BOOST_ASSERT(p.justification_storage_policy_ != nullptr); + BOOST_ASSERT(p.state_pruner_ != nullptr); // Register metrics BOOST_ASSERT(telemetry_ != nullptr); @@ -745,6 +768,7 @@ namespace kagome::blockchain { node->has_justification = true; OUTCOME_TRY(pruneNoLock(p, node)); + OUTCOME_TRY(pruneTrie(p, node->getBlockInfo().number)); p.tree_->updateTreeRoot(node); @@ -840,15 +864,13 @@ namespace kagome::blockchain { }); } - outcome::result BlockTreeImpl::getBlockHash( - primitives::BlockNumber block_number) const { + outcome::result> + BlockTreeImpl::getBlockHash(primitives::BlockNumber block_number) const { return block_tree_data_.sharedAccess( - [&](const auto &p) -> outcome::result { + [&](const auto &p) + -> outcome::result> { OUTCOME_TRY(hash_opt, p.storage_->getBlockHash(block_number)); - if (hash_opt.has_value()) { - return hash_opt.value(); - } - return BlockTreeError::HEADER_NOT_FOUND; + return hash_opt; }); } @@ -1330,10 +1352,12 @@ namespace kagome::blockchain { // remove from storage retired_hashes.reserve(to_remove.size()); for (const auto &node : to_remove) { - OUTCOME_TRY(block_body_res, p.storage_->getBlockBody(node->block_hash)); - if (block_body_res.has_value()) { - extrinsics.reserve(extrinsics.size() + block_body_res.value().size()); - for (auto &ext : block_body_res.value()) { + OUTCOME_TRY(block_header_opt, + p.storage_->getBlockHeader(node->block_hash)); + OUTCOME_TRY(block_body_opt, p.storage_->getBlockBody(node->block_hash)); + if (block_body_opt.has_value()) { + extrinsics.reserve(extrinsics.size() + block_body_opt.value().size()); + for (auto &ext : block_body_opt.value()) { if (auto key = p.extrinsic_event_key_repo_->get( p.hasher_->blake2b_256(ext.data))) { main_thread_.execute([wself{weak_from_this()}, @@ -1349,6 +1373,8 @@ namespace kagome::blockchain { } extrinsics.emplace_back(std::move(ext)); } + BOOST_ASSERT(block_header_opt.has_value()); + OUTCOME_TRY(p.state_pruner_->pruneDiscarded(block_header_opt.value())); } retired_hashes.emplace_back(node->block_hash); @@ -1392,10 +1418,46 @@ namespace kagome::blockchain { return outcome::success(); } + outcome::result BlockTreeImpl::pruneTrie( + const BlockTreeData &block_tree_data, + primitives::BlockNumber new_finalized) { + // pruning is disabled + if (!block_tree_data.state_pruner_->getPruningDepth().has_value()) { + return outcome::success(); + } + auto last_pruned = block_tree_data.state_pruner_->getLastPrunedBlock(); + + BOOST_ASSERT(!last_pruned.has_value() + || last_pruned.value().number + <= block_tree_data.tree_->getRoot().depth); + auto next_pruned_number = last_pruned ? last_pruned->number + 1 : 0; + + OUTCOME_TRY(hash_opt, getBlockHash(next_pruned_number)); + BOOST_ASSERT(hash_opt.has_value()); + primitives::BlockHash hash = std::move(*hash_opt); + auto pruning_depth = + block_tree_data.state_pruner_->getPruningDepth().value_or(0); + if (new_finalized < pruning_depth) { + return outcome::success(); + } + + auto last_to_prune = new_finalized - pruning_depth; + for (auto n = next_pruned_number; n < last_to_prune; n++) { + OUTCOME_TRY(next_hash_opt, getBlockHash(n + 1)); + BOOST_ASSERT(next_hash_opt.has_value()); + auto &next_hash = *next_hash_opt; + OUTCOME_TRY(header, getBlockHeader(hash)); + OUTCOME_TRY(block_tree_data.state_pruner_->pruneFinalized(header)); + hash = std::move(next_hash); + } + + return outcome::success(); + } + outcome::result BlockTreeImpl::reorganizeNoLock(BlockTreeData &p) { auto block = BlockTreeImpl::bestLeafNoLock(p); - // Remove assigning of obsoleted best upper blocks chain + // Remove assigning of obsolete best upper blocks chain auto prev_max_best_block_number = block.number; for (;;) { auto hash_res = diff --git a/core/blockchain/impl/block_tree_impl.hpp b/core/blockchain/impl/block_tree_impl.hpp index 4bdcecd984..a4b42ac53a 100644 --- a/core/blockchain/impl/block_tree_impl.hpp +++ b/core/blockchain/impl/block_tree_impl.hpp @@ -33,6 +33,10 @@ #include "utils/safe_object.hpp" #include "utils/thread_pool.hpp" +namespace kagome::storage::trie_pruner { + class TriePruner; +} + namespace kagome::blockchain { class TreeNode; @@ -54,6 +58,7 @@ namespace kagome::blockchain { extrinsic_event_key_repo, std::shared_ptr justification_storage_policy, + std::shared_ptr state_pruner, std::shared_ptr<::boost::asio::io_context> io_context); /// Recover block tree state at provided block @@ -68,7 +73,7 @@ namespace kagome::blockchain { const primitives::BlockHash &getGenesisBlockHash() const override; - outcome::result getBlockHash( + outcome::result> getBlockHash( primitives::BlockNumber block_number) const override; outcome::result hasBlockHeader( @@ -141,6 +146,7 @@ namespace kagome::blockchain { struct BlockTreeData { std::shared_ptr header_repo_; std::shared_ptr storage_; + std::shared_ptr state_pruner_; std::unique_ptr tree_; std::shared_ptr extrinsic_observer_; std::shared_ptr hasher_; @@ -170,6 +176,7 @@ namespace kagome::blockchain { extrinsic_event_key_repo, std::shared_ptr justification_storage_policy, + std::shared_ptr state_pruner, std::shared_ptr<::boost::asio::io_context> io_context); /** @@ -193,6 +200,9 @@ namespace kagome::blockchain { outcome::result getBlockHeaderNoLock( const BlockTreeData &p, const primitives::BlockHash &block_hash) const; + outcome::result pruneTrie(const BlockTreeData &block_tree_data, + primitives::BlockNumber new_finalized); + outcome::result reorganizeNoLock(BlockTreeData &p); primitives::BlockInfo getLastFinalizedNoLock(const BlockTreeData &p) const; @@ -217,7 +227,45 @@ namespace kagome::blockchain { void notifyChainEventsEngine(primitives::events::ChainEventType event, const primitives::BlockHeader &header); - SafeObject block_tree_data_; + class SafeBlockTreeData { + public: + 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(); + auto reset = gsl::finally([&] { + exclusive_owner_ = std::nullopt; + }); + 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: + SafeObject block_tree_data_; + std::atomic> exclusive_owner_; + + } block_tree_data_; + primitives::events::ExtrinsicSubscriptionEnginePtr extrinsic_events_engine_ = {}; primitives::events::ChainSubscriptionEnginePtr chain_events_engine_ = {}; diff --git a/core/common/buffer.hpp b/core/common/buffer.hpp index 257fae2c0f..44ec8d7a68 100644 --- a/core/common/buffer.hpp +++ b/core/common/buffer.hpp @@ -50,7 +50,7 @@ namespace kagome::common { SLBuffer(const BufferView &s) : Base(s.begin(), s.end()) {} template - SLBuffer(const std::array &other) + explicit SLBuffer(const std::array &other) : Base(other.begin(), other.end()) {} SLBuffer(const uint8_t *begin, const uint8_t *end) : Base(begin, end){}; @@ -206,6 +206,15 @@ namespace kagome::common { return {src.begin(), src.end()}; } + template + bool startsWith(const Prefix& prefix) const { + if (this->size() >= prefix.size()) { + auto this_view = view().subspan(prefix.size()); + return std::equal(this_view.begin(), this_view.end(), std::cbegin(prefix), std::cend(prefix)); + } + return false; + } + using Base::operator==; bool operator==(const BufferView &other) const noexcept { diff --git a/core/common/buffer_or_view.hpp b/core/common/buffer_or_view.hpp index 178d25d6db..291b440e63 100644 --- a/core/common/buffer_or_view.hpp +++ b/core/common/buffer_or_view.hpp @@ -16,6 +16,7 @@ namespace kagome::common { /// Moved owned buffer or readonly view. class BufferOrView { using Span = gsl::span; + template using AsSpan = std::enable_if_t>; @@ -41,7 +42,7 @@ namespace kagome::common { BufferOrView &operator=(BufferOrView &&) = default; /// Is buffer owned. - bool owned() const { + bool isOwned() const { if (variant.which() == 2) { throw std::logic_error{"Tried to use moved BufferOrView"}; } @@ -50,7 +51,7 @@ namespace kagome::common { /// Get view. BufferView view() const { - if (!owned()) { + if (!isOwned()) { return boost::get(variant); } return BufferView{boost::get(variant)}; @@ -68,7 +69,7 @@ namespace kagome::common { /// Get mutable buffer reference. Copy once if view. Buffer &mut() { - if (!owned()) { + if (!isOwned()) { auto view = boost::get(variant); variant = Buffer{view}; } @@ -76,7 +77,7 @@ namespace kagome::common { } /// Move buffer away. Copy once if view. - Buffer into() { + Buffer intoBuffer() { auto buffer = std::move(mut()); variant = Moved{}; return buffer; diff --git a/core/common/size_limited_containers.hpp b/core/common/size_limited_containers.hpp index 7ab426f3df..4c4bc31428 100644 --- a/core/common/size_limited_containers.hpp +++ b/core/common/size_limited_containers.hpp @@ -50,7 +50,7 @@ namespace kagome::common { SizeLimitedContainer() = default; - SizeLimitedContainer(size_t size) + explicit SizeLimitedContainer(size_t size) : Base([&] { if constexpr (size_check_is_enabled) { if (unlikely(size > max_size())) { diff --git a/core/consensus/babe/impl/babe_config_repository_impl.cpp b/core/consensus/babe/impl/babe_config_repository_impl.cpp index 077dc955a9..1efc03e66d 100644 --- a/core/consensus/babe/impl/babe_config_repository_impl.cpp +++ b/core/consensus/babe/impl/babe_config_repository_impl.cpp @@ -105,10 +105,10 @@ namespace kagome::consensus::babe { // First, look up slot number of block number 1 sync epochs if (finalized_block.number > 0) { - OUTCOME_TRY(first_block_hash, block_tree_->getBlockHash(1)); + OUTCOME_TRY(first_block_hash_opt, block_tree_->getBlockHash(1)); OUTCOME_TRY(first_block_header, - block_tree_->getBlockHeader(first_block_hash)); + block_tree_->getBlockHeader(first_block_hash_opt.value())); auto babe_digest_res = getBabeDigests(first_block_header); BOOST_ASSERT_MSG(babe_digest_res.has_value(), @@ -227,7 +227,8 @@ namespace kagome::consensus::babe { block_hash_res.error()); return block_hash_res.as_failure(); } - const auto &block_hash = block_hash_res.value(); + // If no error occurred, hash of a finalized block should be present + const auto &block_hash = *block_hash_res.value(); auto block_header_res = block_tree_->getBlockHeader(block_hash); if (block_header_res.has_error()) { @@ -841,15 +842,31 @@ namespace kagome::consensus::babe { void BabeConfigRepositoryImpl::readFromState( const primitives::BlockInfo &block) { + auto hash1_opt_res = block_tree_->getBlockHash(1); + if (!hash1_opt_res) { + logger_->error( + "readFromState {}, error: {}", block, hash1_opt_res.error()); + return; + } + if (!hash1_opt_res.value().has_value()) { + logger_->error( + "readFromState {}, error: \"Block #1 not present in the storage\"", + block); + return; + } + auto header1_res = block_tree_->getBlockHeader(*hash1_opt_res.value()); + if (!header1_res) { + logger_->error("readFromState {}, error: {}", block, header1_res.error()); + return; + } + if (auto r = readFromStateOutcome(block); not r) { - logger_->error("readFromState {}, error {}", block, r.error()); + logger_->error("readFromState {}, error: {}", block, r.error()); } } outcome::result BabeConfigRepositoryImpl::readFromStateOutcome( const primitives::BlockInfo &block) { - OUTCOME_TRY(hash1, block_tree_->getBlockHash(1)); - OUTCOME_TRY(header1, block_tree_->getBlockHeader(hash1)); OUTCOME_TRY(header, block_tree_->getBlockHeader(block.hash)); auto parent = header; std::optional next_epoch; diff --git a/core/consensus/babe/impl/babe_impl.cpp b/core/consensus/babe/impl/babe_impl.cpp index 29d8b1b16c..7de6faba83 100644 --- a/core/consensus/babe/impl/babe_impl.cpp +++ b/core/consensus/babe/impl/babe_impl.cpp @@ -400,7 +400,7 @@ namespace kagome::consensus::babe { void BabeImpl::adjustEpochDescriptor() { auto first_slot_number = babe_util_->syncEpoch([&]() { auto hash_res = block_tree_->getBlockHash(primitives::BlockNumber(1)); - if (hash_res.has_error()) { + if (hash_res.has_error() || !hash_res.value().has_value()) { SL_TRACE(log_, "First block slot is {}: no first block (at adjusting)", babe_util_->getCurrentSlot()); @@ -408,7 +408,7 @@ namespace kagome::consensus::babe { } auto first_block_header_res = - block_tree_->getBlockHeader(hash_res.value()); + block_tree_->getBlockHeader(*hash_res.value()); if (first_block_header_res.has_error()) { SL_CRITICAL(log_, "Database is not consistent: " @@ -1214,8 +1214,7 @@ namespace kagome::consensus::babe { return common::Buffer{scale::encode(ext).value()}; })); return ext_root_res.has_value() - and (ext_root_res.value() - == common::Buffer(block.header.extrinsics_root)); + and (ext_root_res.value() == block.header.extrinsics_root); }(), "Extrinsics root does not match extrinsics in the block"); @@ -1341,8 +1340,8 @@ namespace kagome::consensus::babe { current_epoch_.start_slot = current_slot_; babe_util_->syncEpoch([&]() { - auto hash_res = block_tree_->getBlockHash(primitives::BlockNumber(1)); - if (hash_res.has_error()) { + auto hash_opt_res = block_tree_->getBlockHash(primitives::BlockNumber(1)); + if (hash_opt_res.has_error() || !hash_opt_res.value().has_value()) { SL_TRACE(log_, "First block slot is {}: no first block (at start next epoch)", babe_util_->getCurrentSlot()); @@ -1350,7 +1349,7 @@ namespace kagome::consensus::babe { } auto first_block_header_res = - block_tree_->getBlockHeader(hash_res.value()); + block_tree_->getBlockHeader(*hash_opt_res.value()); if (first_block_header_res.has_error()) { SL_CRITICAL(log_, "Database is not consistent: " diff --git a/core/consensus/babe/impl/block_appender_base.cpp b/core/consensus/babe/impl/block_appender_base.cpp index 4fb1475511..cf29b9ee8f 100644 --- a/core/consensus/babe/impl/block_appender_base.cpp +++ b/core/consensus/babe/impl/block_appender_base.cpp @@ -155,7 +155,7 @@ namespace kagome::consensus::babe { babe_util_->syncEpoch([&] { auto hash_res = block_tree_->getBlockHash(primitives::BlockNumber(1)); - if (hash_res.has_error()) { + if (hash_res.has_error() || !hash_res.value().has_value()) { if (block.header.number == 1) { SL_TRACE(logger_, "First block slot is {}: it is first block (at executing)", @@ -170,7 +170,7 @@ namespace kagome::consensus::babe { } auto first_block_header_res = - block_tree_->getBlockHeader(hash_res.value()); + block_tree_->getBlockHeader(*hash_res.value()); if (first_block_header_res.has_error()) { SL_CRITICAL(logger_, "Database is not consistent: " diff --git a/core/consensus/grandpa/impl/authority_manager_impl.cpp b/core/consensus/grandpa/impl/authority_manager_impl.cpp index 800e6220ff..8894bf80f9 100644 --- a/core/consensus/grandpa/impl/authority_manager_impl.cpp +++ b/core/consensus/grandpa/impl/authority_manager_impl.cpp @@ -211,7 +211,8 @@ namespace kagome::consensus::grandpa { block_hash_res.error()); return block_hash_res.as_failure(); } - const auto &block_hash = block_hash_res.value(); + // a finalized block should be present + const auto &block_hash = *block_hash_res.value(); auto block_header_res = block_tree_->getBlockHeader(block_hash); if (block_header_res.has_error()) { @@ -989,7 +990,6 @@ namespace kagome::consensus::grandpa { "Looking if direct chain exists between {} and {}", ancestor, descendant); - KAGOME_PROFILE_START(direct_chain_exists) // Check if it's one-block chain if (ancestor == descendant) { return true; @@ -1003,7 +1003,6 @@ namespace kagome::consensus::grandpa { return false; } auto result = block_tree_->hasDirectChain(ancestor.hash, descendant.hash); - KAGOME_PROFILE_END(direct_chain_exists) return result; } diff --git a/core/consensus/grandpa/impl/grandpa_impl.cpp b/core/consensus/grandpa/impl/grandpa_impl.cpp index 60244e86bc..b70619f83b 100644 --- a/core/consensus/grandpa/impl/grandpa_impl.cpp +++ b/core/consensus/grandpa/impl/grandpa_impl.cpp @@ -5,9 +5,10 @@ #include "consensus/grandpa/impl/grandpa_impl.hpp" +#include + #include #include -#include #include "application/app_state_manager.hpp" #include "application/chain_spec.hpp" @@ -553,7 +554,7 @@ namespace kagome::consensus::grandpa { return; } if (res.has_error()) { - SL_WARN(self->logger_, + SL_DEBUG(self->logger_, "Missing justifications between blocks {} and " "{} was not loaded: {}", last_finalized, diff --git a/core/host_api/impl/storage_extension.cpp b/core/host_api/impl/storage_extension.cpp index 9aa2be5651..ace7980af0 100644 --- a/core/host_api/impl/storage_extension.cpp +++ b/core/host_api/impl/storage_extension.cpp @@ -137,7 +137,9 @@ namespace kagome::host_api { if (result) { SL_TRACE_FUNC_CALL(logger_, result.value(), key_buffer); } else { - logger_->error(error_message, key_buffer.toHex(), result.error()); + auto msg = fmt::format(error_message, key_buffer.toHex(), result.error()); + SL_DEBUG(logger_, "{}", msg); + throw std::runtime_error(msg); } auto &option = result.value(); @@ -352,12 +354,12 @@ namespace kagome::host_api { auto res = memory.storeBuffer(storage::trie::kEmptyRootHash); return runtime::PtrSize(res).ptr; } - storage::trie::PolkadotTrieImpl trie; + auto trie = storage::trie::PolkadotTrieImpl::createEmpty(); for (auto &&p : pv) { auto &&key = p.first; common::BufferView value = p.second; // already scale-encoded - auto put_res = trie.put(key, value); + auto put_res = trie->put(key, value); if (not put_res) { logger_->error( "Insertion of value {} with key {} into the trie failed due to " @@ -368,7 +370,7 @@ namespace kagome::host_api { } } const auto &enc = - codec.encodeNode(*trie.getRoot(), storage::trie::StateVersion::V0, {}); + codec.encodeNode(*trie->getRoot(), storage::trie::StateVersion::V0, {}); if (!enc) { logger_->error("failed to encode trie root: {}", enc.error()); throw std::runtime_error(enc.error().message()); diff --git a/core/injector/application_injector.cpp b/core/injector/application_injector.cpp index 4a43ba75cf..421b5dbaf4 100644 --- a/core/injector/application_injector.cpp +++ b/core/injector/application_injector.cpp @@ -161,6 +161,7 @@ #include "storage/trie/polkadot_trie/polkadot_trie_factory_impl.hpp" #include "storage/trie/serialization/polkadot_codec.hpp" #include "storage/trie/serialization/trie_serializer_impl.hpp" +#include "storage/trie_pruner/impl/trie_pruner_impl.hpp" #include "telemetry/impl/service_impl.hpp" #include "transaction_pool/impl/pool_moderator_impl.hpp" #include "transaction_pool/impl/transaction_pool_impl.hpp" @@ -278,6 +279,8 @@ namespace { injector.template create>(); auto storage = injector.template create>(); + auto state_pruner = + injector.template create>(); auto extrinsic_observer = injector.template create>(); @@ -304,6 +307,7 @@ namespace { std::move(ext_events_engine), std::move(ext_events_key_repo), std::move(justification_storage_policy), + std::move(state_pruner), injector.template create>()); if (not block_tree_res.has_value()) { @@ -717,12 +721,15 @@ namespace { sptr>(), injector.template create>(), injector.template create< - sptr>()) + sptr>(), + injector.template create< + sptr>()) .value(); }), di::bind.template to(), di::bind.template to(), di::bind.template to(), + di::bind.template to(), di::bind.template to(), bind_by_lambda([](const auto &injector) { const application::AppConfiguration &config = diff --git a/core/injector/calculate_genesis_state.hpp b/core/injector/calculate_genesis_state.hpp index 0f8c082b0d..04fa3511c8 100644 --- a/core/injector/calculate_genesis_state.hpp +++ b/core/injector/calculate_genesis_state.hpp @@ -11,6 +11,7 @@ #include "storage/predefined_keys.hpp" #include "storage/trie/polkadot_trie/polkadot_trie_impl.hpp" #include "storage/trie/serialization/trie_serializer.hpp" +#include "storage/trie_pruner/trie_pruner.hpp" namespace kagome::injector { inline outcome::result calculate_genesis_state( @@ -18,14 +19,14 @@ namespace kagome::injector { const runtime::ModuleFactory &module_factory, storage::trie::TrieSerializer &trie_serializer) { auto trie_from = [](const application::GenesisRawData &kv) { - storage::trie::PolkadotTrieImpl trie; + auto trie = storage::trie::PolkadotTrieImpl::createEmpty(); for (auto &[k, v] : kv) { - trie.put(k, common::BufferView{v}).value(); + trie->put(k, common::BufferView{v}).value(); } return trie; }; auto top_trie = trie_from(chain_spec.getGenesisTopSection()); - OUTCOME_TRY(code, top_trie.get(storage::kRuntimeCodeKey)); + OUTCOME_TRY(code, top_trie->get(storage::kRuntimeCodeKey)); OUTCOME_TRY(env, runtime::RuntimeEnvironment::fromCode(module_factory, code)); runtime::CoreImpl core_api{ @@ -34,15 +35,20 @@ namespace kagome::injector { }; OUTCOME_TRY(runtime_version, core_api.version(env)); auto version = storage::trie::StateVersion{runtime_version.state_version}; + std::vector> child_tries; for (auto &[child, kv] : chain_spec.getGenesisChildrenDefaultSection()) { - auto trie = trie_from(kv); - OUTCOME_TRY(root, trie_serializer.storeTrie(trie, version)); + child_tries.emplace_back(trie_from(kv)); + OUTCOME_TRY(root, trie_serializer.storeTrie(*child_tries.back(), version)); + common::Buffer child2; child2 += storage::kChildStorageDefaultPrefix; child2 += child; - top_trie.put(child2, common::BufferView{root}).value(); + top_trie->put(child2, common::BufferView{root}).value(); } - return trie_serializer.storeTrie(top_trie, version); + + OUTCOME_TRY(trie_hash, trie_serializer.storeTrie(*top_trie, version)); + + return trie_hash; } } // namespace kagome::injector diff --git a/core/log/configurator.cpp b/core/log/configurator.cpp index 8dd2bb5988..6026904a7b 100644 --- a/core/log/configurator.cpp +++ b/core/log/configurator.cpp @@ -105,6 +105,7 @@ namespace kagome::log { - name: storage children: - name: trie + - name: trie_pruner - name: transactions - name: pubsub - name: others diff --git a/core/log/profiling_logger.hpp b/core/log/profiling_logger.hpp index e1f8cd2265..73682a0642 100644 --- a/core/log/profiling_logger.hpp +++ b/core/log/profiling_logger.hpp @@ -8,31 +8,72 @@ #include "log/logger.hpp" +#include "clock/impl/clock_impl.hpp" + namespace kagome::log { + extern Logger profiling_logger; -} + + struct ProfileScope { + using Clock = ::kagome::clock::SteadyClockImpl; + + ProfileScope(std::string_view scope, log::Logger logger) + : scope{scope}, logger{logger} { + BOOST_ASSERT(logger != nullptr); + start = Clock{}.now(); + } + + ProfileScope(ProfileScope &&) = delete; + ProfileScope(ProfileScope const &) = delete; + ProfileScope &operator=(ProfileScope const &) = delete; + ProfileScope &operator=(ProfileScope &&) = delete; + + ~ProfileScope() { + if (!done) { + end(); + } + } + + void end() { + done = true; + auto end = Clock{}.now(); + SL_DEBUG( + logger, + "{} took {} ms", + scope, + ::std::chrono::duration_cast<::std::chrono::milliseconds>(end - start) + .count()); + } + + private: + bool done = false; + std::string_view scope{}; + Clock::TimePoint start; + log::Logger logger; + }; +} // namespace kagome::log #ifdef KAGOME_PROFILING -#include "clock/impl/clock_impl.hpp" +#define KAGOME_PROFILE_START_L(logger, scope) \ + auto _profiling_scope_##scope = ::kagome::log::ProfileScope{#scope, logger}; -#define KAGOME_PROFILE_START(scope) \ - auto _profiling_start_##scope = ::kagome::clock::SteadyClockImpl{}.now(); +#define KAGOME_PROFILE_END_L(logger, scope) \ + _profiling_scope_##scope .end(); -#define KAGOME_PROFILE_END(scope) \ - auto _profiling_end_##scope = ::kagome::clock::SteadyClockImpl{}.now(); \ - SL_DEBUG(::kagome::log::profiling_logger, \ - "{} took {} ms", \ - #scope, \ - ::std::chrono::duration_cast<::std::chrono::milliseconds>( \ - _profiling_end_##scope - _profiling_start_##scope) \ - .count()); +#define KAGOME_PROFILE_START(scope) \ + KAGOME_PROFILE_START_L(::kagome::log::profiling_logger, scope) +#define KAGOME_PROFILE_END(scope) \ + KAGOME_PROFILE_END_L(::kagome::log::profiling_logger, scope) #else #define KAGOME_PROFILE_START(scope) #define KAGOME_PROFILE_END(scope) +#define KAGOME_PROFILE_START_L(logger, scope) +#define KAGOME_PROFILE_END_L(logger, scope) + #endif #endif // KAGOME_CORE_LOG_PROFILING_LOGGER_HPP diff --git a/core/network/impl/protocols/parachain_protocol.hpp b/core/network/impl/protocols/parachain_protocol.hpp index 37c20281d3..60740b514f 100644 --- a/core/network/impl/protocols/parachain_protocol.hpp +++ b/core/network/impl/protocols/parachain_protocol.hpp @@ -224,7 +224,7 @@ namespace kagome::network { } if (!result) { - SL_WARN( + SL_DEBUG( self->base_.logger(), "Can't read incoming collation message from stream {} with " "error {}", diff --git a/core/network/impl/protocols/protocol_base_impl.hpp b/core/network/impl/protocols/protocol_base_impl.hpp index e196c56eb7..0591e15c25 100644 --- a/core/network/impl/protocols/protocol_base_impl.hpp +++ b/core/network/impl/protocols/protocol_base_impl.hpp @@ -92,7 +92,7 @@ namespace kagome::network { stream->close([log{logger()}, wptr, stream](auto &&result) { if (auto self = wptr.lock()) { if (!result) { - SL_WARN(log, + SL_DEBUG(log, "Stream {} was not closed successfully with {}", self->protocolName(), stream->remotePeerId().value()); diff --git a/core/network/impl/state_sync_request_flow.cpp b/core/network/impl/state_sync_request_flow.cpp index b7fc9c38e4..d08c0568ae 100644 --- a/core/network/impl/state_sync_request_flow.cpp +++ b/core/network/impl/state_sync_request_flow.cpp @@ -9,6 +9,7 @@ #include "runtime/runtime_api/core.hpp" #include "storage/predefined_keys.hpp" #include "storage/trie/serialization/trie_serializer.hpp" +#include "storage/trie_pruner/trie_pruner.hpp" OUTCOME_CPP_DEFINE_CATEGORY(kagome::network, StateSyncRequestFlow::Error, e) { using E = decltype(e); @@ -23,10 +24,12 @@ OUTCOME_CPP_DEFINE_CATEGORY(kagome::network, StateSyncRequestFlow::Error, e) { namespace kagome::network { StateSyncRequestFlow::StateSyncRequestFlow( + std::shared_ptr state_pruner, const primitives::BlockInfo &block_info, const primitives::BlockHeader &block) - : block_info_{block_info}, block_{block} { - roots_[std::nullopt]; + : state_pruner_{state_pruner}, block_info_{block_info}, block_{block} { + BOOST_ASSERT(state_pruner_ != nullptr); + roots_.insert({std::nullopt, Root{}}); } bool StateSyncRequestFlow::complete() const { @@ -34,13 +37,13 @@ namespace kagome::network { } StateRequest StateSyncRequestFlow::nextRequest() const { - assert(not complete()); + BOOST_ASSERT(not complete()); return StateRequest{block_info_.hash, last_key_, true}; } outcome::result StateSyncRequestFlow::onResponse( const StateResponse &res) { - assert(not complete()); + BOOST_ASSERT(not complete()); auto empty = true; for (auto &entry : res.entries) { if (not entry.entries.empty() or entry.complete) { @@ -71,7 +74,7 @@ namespace kagome::network { OUTCOME_TRY(hash, RootHash::fromSpan(value)); roots_[hash].children.emplace_back(key); } else { - root.trie.put(key, common::BufferView{value}).value(); + root.trie->put(key, common::BufferView{value}).value(); } } if (entry.complete) { @@ -85,9 +88,9 @@ namespace kagome::network { const runtime::ModuleFactory &module_factory, runtime::Core &core_api, storage::trie::TrieSerializer &trie_serializer) { - assert(complete()); + BOOST_ASSERT(complete()); auto &top = roots_[std::nullopt]; - OUTCOME_TRY(code, top.trie.get(storage::kRuntimeCodeKey)); + OUTCOME_TRY(code, top.trie->get(storage::kRuntimeCodeKey)); OUTCOME_TRY(env, runtime::RuntimeEnvironment::fromCode(module_factory, code)); OUTCOME_TRY(runtime_version, core_api.version(env)); @@ -96,15 +99,17 @@ namespace kagome::network { if (not expected) { continue; } - OUTCOME_TRY(actual, trie_serializer.storeTrie(root.trie, version)); + OUTCOME_TRY(actual, trie_serializer.storeTrie(*root.trie, version)); + OUTCOME_TRY(state_pruner_->addNewState(*root.trie, version)); if (actual != expected) { return Error::HASH_MISMATCH; } for (auto &child : root.children) { - top.trie.put(child, common::BufferView{actual}).value(); + top.trie->put(child, common::BufferView{actual}).value(); } } - OUTCOME_TRY(actual, trie_serializer.storeTrie(top.trie, version)); + OUTCOME_TRY(actual, trie_serializer.storeTrie(*top.trie, version)); + OUTCOME_TRY(state_pruner_->addNewState(*top.trie, version)); if (actual != block_.state_root) { return Error::HASH_MISMATCH; } diff --git a/core/network/impl/state_sync_request_flow.hpp b/core/network/impl/state_sync_request_flow.hpp index 838b823518..9c0c7ba490 100644 --- a/core/network/impl/state_sync_request_flow.hpp +++ b/core/network/impl/state_sync_request_flow.hpp @@ -23,6 +23,10 @@ namespace kagome::storage::trie { class TrieSerializer; } // namespace kagome::storage::trie +namespace kagome::storage::trie_pruner { + class TriePruner; +} // namespace kagome::storage::trie_pruner + namespace kagome::primitives { struct BlockHeader; } // namespace kagome::primitives @@ -34,7 +38,8 @@ namespace kagome::network { class StateSyncRequestFlow { using RootHash = storage::trie::RootHash; struct Root { - storage::trie::PolkadotTrieImpl trie; + std::shared_ptr trie = + storage::trie::PolkadotTrieImpl::createEmpty(); std::vector children; }; @@ -44,8 +49,10 @@ namespace kagome::network { HASH_MISMATCH, }; - StateSyncRequestFlow(const primitives::BlockInfo &block_info, - const primitives::BlockHeader &block); + StateSyncRequestFlow( + std::shared_ptr state_pruner, + const primitives::BlockInfo &block_info, + const primitives::BlockHeader &block); auto &blockInfo() const { return block_info_; @@ -63,6 +70,8 @@ namespace kagome::network { storage::trie::TrieSerializer &trie_serializer); private: + std::shared_ptr state_pruner_; + primitives::BlockInfo block_info_; primitives::BlockHeader block_; diff --git a/core/network/impl/stream_engine.hpp b/core/network/impl/stream_engine.hpp index c1917ec37f..96e8b6a98a 100644 --- a/core/network/impl/stream_engine.hpp +++ b/core/network/impl/stream_engine.hpp @@ -13,6 +13,8 @@ #include #include +#include + #include "libp2p/connection/stream.hpp" #include "libp2p/host/host.hpp" #include "libp2p/peer/peer_info.hpp" @@ -267,6 +269,8 @@ namespace kagome::network { private: struct ProtocolDescr { std::shared_ptr protocol; + log::Logger logger = + log::createLogger("ProtoDescription", "stream_engine"); struct { std::shared_ptr stream; @@ -307,6 +311,7 @@ namespace kagome::network { } outgoing.reserved = true; + //bt(); return true; } @@ -318,8 +323,9 @@ namespace kagome::network { * Drops the flag that outgoing stream establishing. */ void dropReserved() { - BOOST_ASSERT(outgoing.reserved); + //BOOST_ASSERT(outgoing.reserved); outgoing.reserved = false; + //bt(); } /** diff --git a/core/network/impl/synchronizer_impl.cpp b/core/network/impl/synchronizer_impl.cpp index acae8102bb..82269c1d36 100644 --- a/core/network/impl/synchronizer_impl.cpp +++ b/core/network/impl/synchronizer_impl.cpp @@ -83,6 +83,7 @@ namespace kagome::network { std::shared_ptr block_executor, std::shared_ptr serializer, std::shared_ptr storage, + std::shared_ptr trie_pruner, std::shared_ptr router, std::shared_ptr scheduler, std::shared_ptr hasher, @@ -97,6 +98,7 @@ namespace kagome::network { block_executor_(std::move(block_executor)), serializer_(std::move(serializer)), storage_(std::move(storage)), + trie_pruner_(std::move(trie_pruner)), router_(std::move(router)), scheduler_(std::move(scheduler)), hasher_(std::move(hasher)), @@ -109,6 +111,7 @@ namespace kagome::network { BOOST_ASSERT(block_executor_); BOOST_ASSERT(serializer_); BOOST_ASSERT(storage_); + BOOST_ASSERT(trie_pruner_); BOOST_ASSERT(router_); BOOST_ASSERT(scheduler_); BOOST_ASSERT(hasher_); @@ -844,7 +847,7 @@ namespace kagome::network { std::make_tuple(peer_id, request_fingerprint), "load justifications"); not r.second) { - SL_ERROR(log_, + SL_DEBUG(log_, "Can't load justification from {} for block {}: Duplicate '{}' " "request", peer_id, @@ -870,7 +873,7 @@ namespace kagome::network { } if (response_res.has_error()) { - SL_ERROR(self->log_, + SL_DEBUG(self->log_, "Can't load justification from {} for block {}: {}", peer_id, target_block, @@ -974,9 +977,16 @@ namespace kagome::network { const primitives::BlockInfo &_block, CbResultVoid &&cb) { auto block = _block; + + auto hash_res = block_tree_->getBlockHash(1); + if (!hash_res) { + SL_ERROR( + log_, "Error retrieving the first block hash: {}", hash_res.error()); + return; + } + auto& hash_opt = hash_res.value(); // BabeConfigRepositoryImpl first block slot - if (auto hash = block_tree_->getBlockHash(1); - not hash or not block_tree_->getBlockHeader(hash.value())) { + if (not hash_opt or not block_tree_->getBlockHeader(hash_opt.value())) { auto cb2 = [=, cb{std::move(cb)}, weak{weak_from_this()}]( outcome::result _res) mutable { auto self = weak.lock(); @@ -1106,7 +1116,7 @@ namespace kagome::network { return; } if (not state_sync_flow_ or state_sync_flow_->blockInfo() != block) { - state_sync_flow_.emplace(block, header); + state_sync_flow_.emplace(trie_pruner_, block, header); } state_sync_.emplace(StateSync{ peer_id, @@ -1352,12 +1362,12 @@ namespace kagome::network { return; } - SL_WARN(self->log_, - "Missing justifications between blocks {} and " - "{} was not loaded: {}", - last_finalized, - block_info.number, - res.error()); + SL_DEBUG(self->log_, + "Missing justifications between blocks {} and " + "{} was not loaded: {}", + last_finalized, + block_info.number, + res.error()); } }); } diff --git a/core/network/impl/synchronizer_impl.hpp b/core/network/impl/synchronizer_impl.hpp index d15d19516f..282df8f252 100644 --- a/core/network/impl/synchronizer_impl.hpp +++ b/core/network/impl/synchronizer_impl.hpp @@ -28,6 +28,10 @@ namespace kagome::application { class AppConfiguration; } +namespace kagome::storage::trie_pruner { + class TriePruner; +} + namespace kagome::consensus::babe { class BlockHeaderAppender; class BlockExecutor; @@ -91,6 +95,7 @@ namespace kagome::network { std::shared_ptr block_executor, std::shared_ptr serializer, std::shared_ptr storage, + std::shared_ptr trie_pruner, std::shared_ptr router, std::shared_ptr scheduler, std::shared_ptr hasher, @@ -217,6 +222,7 @@ namespace kagome::network { std::shared_ptr block_executor_; std::shared_ptr serializer_; std::shared_ptr storage_; + std::shared_ptr trie_pruner_; std::shared_ptr router_; std::shared_ptr scheduler_; std::shared_ptr hasher_; diff --git a/core/offchain/impl/offchain_local_storage.cpp b/core/offchain/impl/offchain_local_storage.cpp index 32c485c600..f84965c436 100644 --- a/core/offchain/impl/offchain_local_storage.cpp +++ b/core/offchain/impl/offchain_local_storage.cpp @@ -88,6 +88,6 @@ namespace kagome::offchain { auto iKey = internalKey(key); OUTCOME_TRY(value, storage_->get(iKey)); - return value.into(); + return value.intoBuffer(); } } // namespace kagome::offchain diff --git a/core/offchain/impl/offchain_persistent_storage.cpp b/core/offchain/impl/offchain_persistent_storage.cpp index 5af7134fa2..43c9260acd 100644 --- a/core/offchain/impl/offchain_persistent_storage.cpp +++ b/core/offchain/impl/offchain_persistent_storage.cpp @@ -67,7 +67,7 @@ namespace kagome::offchain { const common::BufferView &key) { auto iKey = internalKey(key); OUTCOME_TRY(value, storage_->get(iKey)); - return value.into(); + return value.intoBuffer(); } } // namespace kagome::offchain diff --git a/core/parachain/approval/approval_distribution.cpp b/core/parachain/approval/approval_distribution.cpp index 02e25c1c43..24e92ee684 100644 --- a/core/parachain/approval/approval_distribution.cpp +++ b/core/parachain/approval/approval_distribution.cpp @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include #include #include "clock/impl/basic_waitable_timer.hpp" @@ -22,8 +21,6 @@ #include "utils/async_sequence.hpp" #include "utils/weak_from_shared.hpp" -#define _STRINGIZE(s) #s - OUTCOME_CPP_DEFINE_CATEGORY(kagome::parachain, ApprovalDistribution::Error, e) { using E = kagome::parachain::ApprovalDistribution::Error; switch (e) { diff --git a/core/parachain/availability/bitfield/signer.cpp b/core/parachain/availability/bitfield/signer.cpp index ba82c9656e..9f0031b6b8 100644 --- a/core/parachain/availability/bitfield/signer.cpp +++ b/core/parachain/availability/bitfield/signer.cpp @@ -52,7 +52,7 @@ namespace kagome::parachain { boost::get(event)) .value())); if (r.has_error()) { - SL_WARN(log(), "onBlock error {}", r.error()); + SL_DEBUG(log(), "onBlock error {}", r.error()); } } }); diff --git a/core/parachain/availability/proof.hpp b/core/parachain/availability/proof.hpp index f91416ee31..7bc593bae5 100644 --- a/core/parachain/availability/proof.hpp +++ b/core/parachain/availability/proof.hpp @@ -6,6 +6,8 @@ #ifndef KAGOME_PARACHAIN_AVAILABILITY_PROOF_HPP #define KAGOME_PARACHAIN_AVAILABILITY_PROOF_HPP +#include + #include "network/types/collator_messages.hpp" #include "parachain/availability/erasure_coding_error.hpp" #include "storage/trie/polkadot_trie/polkadot_trie_impl.hpp" @@ -20,7 +22,7 @@ namespace kagome::parachain { std::vector &chunks) { storage::trie::PolkadotCodec codec; - auto trie = std::make_shared(); + auto trie = storage::trie::PolkadotTrieImpl::createEmpty(); for (size_t i = 0; i < chunks.size(); ++i) { if (chunks[i].index != i) { throw std::logic_error{"ErasureChunk.index is wrong"}; @@ -30,8 +32,12 @@ namespace kagome::parachain { using Ptr = const storage::trie::TrieNode *; std::unordered_map db; - auto store = [&](Ptr ptr, common::BufferView, common::Buffer &&encoded) { - db.emplace(ptr, std::move(encoded)); + auto store = [&](storage::trie::Codec::Visitee visitee) { + if (auto child_data = + std::get_if(&visitee); + child_data != nullptr) { + db.emplace(&child_data->child, std::move(child_data->encoding)); + } return outcome::success(); }; auto root_encoded = @@ -42,9 +48,10 @@ namespace kagome::parachain { for (auto &chunk : chunks) { network::ChunkProof proof{root_encoded}; - auto visit = [&](const storage::trie::BranchNode &node, uint8_t index) { - auto child = dynamic_cast(node.children.at(index).get()); - if (auto it = db.find(child); it != db.end()) { + auto visit = [&](const storage::trie::BranchNode &node, + uint8_t index, + const storage::trie::TrieNode &child) { + if (auto it = db.find(&child); it != db.end()) { proof.emplace_back(it->second); } return outcome::success(); @@ -75,24 +82,32 @@ namespace kagome::parachain { -> outcome::result> { auto merkle = dynamic_cast(*node).db_key; - if (merkle.empty() or merkle == storage::trie::kEmptyRootHash) { + // dummy node's key is always a hash + if (merkle.asHash() == storage::trie::kEmptyRootHash) { return nullptr; } - if (codec.isMerkleHash(merkle)) { - auto it = db.find(common::Hash256::fromSpan(merkle).value()); + if (merkle.isHash()) { + auto it = db.find(*merkle.asHash()); if (it == db.end()) { return ErasureCodingRootError::MISMATCH; } - merkle = it->second; + OUTCOME_TRY(n, codec.decodeNode(it->second)); + return std::dynamic_pointer_cast(n); } - OUTCOME_TRY(n, codec.decodeNode(merkle)); + OUTCOME_TRY(n, codec.decodeNode(merkle.asBuffer())); return std::dynamic_pointer_cast(n); }; + auto load_value = [](const common::Hash256 &hash) + -> outcome::result> { + BOOST_ASSERT_MSG(false, "Hashed values should not appear in proofs"); + return std::nullopt; + }; OUTCOME_TRY(root, load(std::make_shared(root_hash))); - storage::trie::PolkadotTrieImpl trie{root, load}; + auto trie = + storage::trie::PolkadotTrieImpl::create(root, {load, load_value}); - OUTCOME_TRY(_expected, trie.get(makeTrieProofKey(chunk.index))); + OUTCOME_TRY(_expected, trie->get(makeTrieProofKey(chunk.index))); OUTCOME_TRY(expected, common::Hash256::fromSpan(_expected)); auto actual = codec.hash256(chunk.chunk); if (actual != expected) { diff --git a/core/runtime/wavm/module.cpp b/core/runtime/wavm/module.cpp index 1decf06973..864c36bbad 100644 --- a/core/runtime/wavm/module.cpp +++ b/core/runtime/wavm/module.cpp @@ -32,7 +32,6 @@ namespace kagome::runtime::wavm { WAVM::IR::FeatureSpec featureSpec; featureSpec.extendedNameSection = true; - log::Logger logger = log::createLogger("WAVM Module", "wavm"); logger->info( "Compiling WebAssembly module for Runtime (going to take a few dozens " diff --git a/core/storage/CMakeLists.txt b/core/storage/CMakeLists.txt index 93f3895d69..b3b59c8aa1 100644 --- a/core/storage/CMakeLists.txt +++ b/core/storage/CMakeLists.txt @@ -24,6 +24,8 @@ add_library(storage trie/polkadot_trie/trie_error.cpp trie/serialization/trie_serializer_impl.cpp trie/serialization/polkadot_codec.cpp + trie_pruner/impl/trie_pruner_impl.cpp + trie_pruner/impl/recover_pruner_state.cpp ) target_link_libraries(storage blob diff --git a/core/storage/face/generic_maps.hpp b/core/storage/face/generic_maps.hpp index 0077ae8099..833a1f1beb 100644 --- a/core/storage/face/generic_maps.hpp +++ b/core/storage/face/generic_maps.hpp @@ -26,8 +26,8 @@ namespace kagome::storage::face { * Reports RAM state size * @return size in bytes */ - virtual size_t size() const { - throw std::logic_error{"GenericStorage::size not implemented"}; + virtual std::optional byteSizeHint() const { + return std::nullopt; } }; diff --git a/core/storage/in_memory/in_memory_batch.hpp b/core/storage/in_memory/in_memory_batch.hpp index c56650786e..b1577b67ff 100644 --- a/core/storage/in_memory/in_memory_batch.hpp +++ b/core/storage/in_memory/in_memory_batch.hpp @@ -18,7 +18,7 @@ namespace kagome::storage { outcome::result put(const BufferView &key, BufferOrView &&value) override { - entries[key.toHex()] = value.into(); + entries[key.toHex()] = value.intoBuffer(); return outcome::success(); } diff --git a/core/storage/in_memory/in_memory_storage.cpp b/core/storage/in_memory/in_memory_storage.cpp index b9b67cf7d3..b0271a339c 100644 --- a/core/storage/in_memory/in_memory_storage.cpp +++ b/core/storage/in_memory/in_memory_storage.cpp @@ -39,7 +39,7 @@ namespace kagome::storage { size_ -= old_value_size; } size_ += value.size(); - storage[key.toHex()] = value.into(); + storage[key.toHex()] = value.intoBuffer(); return outcome::success(); } @@ -68,7 +68,7 @@ namespace kagome::storage { return nullptr; } - size_t InMemoryStorage::size() const { + std::optional InMemoryStorage::byteSizeHint() const { return size_; } } // namespace kagome::storage diff --git a/core/storage/in_memory/in_memory_storage.hpp b/core/storage/in_memory/in_memory_storage.hpp index 6d3d4f929d..5133ee88c0 100644 --- a/core/storage/in_memory/in_memory_storage.hpp +++ b/core/storage/in_memory/in_memory_storage.hpp @@ -43,7 +43,7 @@ namespace kagome::storage { std::unique_ptr cursor() override; - size_t size() const override; + std::optional byteSizeHint() const override; private: std::map storage; diff --git a/core/storage/rocksdb/rocksdb.cpp b/core/storage/rocksdb/rocksdb.cpp index 070a6e841b..b5e836d29b 100644 --- a/core/storage/rocksdb/rocksdb.cpp +++ b/core/storage/rocksdb/rocksdb.cpp @@ -184,7 +184,7 @@ namespace kagome::storage { return std::make_unique(*this); } - size_t RocksDbSpace::size() const { + std::optional RocksDbSpace::byteSizeHint() const { auto rocks = storage_.lock(); if (!rocks) { return 0; @@ -262,9 +262,10 @@ namespace kagome::storage { std::string value; auto status = rocks->db_->Get(rocks->ro_, column_, make_slice(key), &value); if (status.ok()) { - return std::make_optional(Buffer( - reinterpret_cast(value.data()), // NOLINT - reinterpret_cast(value.data()) + value.size())); // NOLINT + auto buf = Buffer( + reinterpret_cast(value.data()), // NOLINT + reinterpret_cast(value.data()) + value.size()); // NOLINT + return std::make_optional(BufferOrView(std::move(buf))); } if (status.IsNotFound()) { diff --git a/core/storage/rocksdb/rocksdb.hpp b/core/storage/rocksdb/rocksdb.hpp index fbda6d5b19..f6a027f85e 100644 --- a/core/storage/rocksdb/rocksdb.hpp +++ b/core/storage/rocksdb/rocksdb.hpp @@ -95,7 +95,7 @@ namespace kagome::storage { std::unique_ptr batch() override; - size_t size() const override; + std::optional byteSizeHint() const override; std::unique_ptr cursor() override; diff --git a/core/storage/rocksdb/rocksdb_spaces.cpp b/core/storage/rocksdb/rocksdb_spaces.cpp index c3ccd37622..e884dc69f7 100644 --- a/core/storage/rocksdb/rocksdb_spaces.cpp +++ b/core/storage/rocksdb/rocksdb_spaces.cpp @@ -6,23 +6,29 @@ #include "storage/rocksdb/rocksdb_spaces.hpp" #include -#include #include +#include namespace kagome::storage { std::string spaceName(Space space) { - static const std::vector names = { - rocksdb::kDefaultColumnFamilyName, + static constexpr std::array kNames{ "lookup_key", "header", "block_body", "justification", "trie_node", }; - assert(names.size() == Space::kTotal); - assert(space < Space::kTotal); + static_assert(kNames.size() == Space::kTotal - 1); + + static const std::vector names = []() { + std::vector names; + names.push_back(rocksdb::kDefaultColumnFamilyName); + names.insert(names.end(), kNames.begin(), kNames.end()); + return names; + }(); + BOOST_ASSERT(space < Space::kTotal); return names.at(space); } diff --git a/core/storage/trie/codec.hpp b/core/storage/trie/codec.hpp deleted file mode 100644 index 1ec7ef7a71..0000000000 --- a/core/storage/trie/codec.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef KAGOME_TRIE_CODEC_HPP -#define KAGOME_TRIE_CODEC_HPP - -#include "common/blob.hpp" -#include "common/buffer.hpp" -#include "storage/trie/node.hpp" -#include "storage/trie/types.hpp" - -namespace kagome::storage::trie { - struct TrieNode; - - /** - * @brief Internal codec for nodes in the Trie. Eth and substrate have - * different codecs, but rest of the code should be same. - */ - class Codec { - public: - using StoreChildren = std::function( - const TrieNode *, common::BufferView, common::Buffer &&)>; - - virtual ~Codec() = default; - - /** - * @brief Encode node to byte representation and store children - * @param node node in the trie - * @param store_children chidren storer - * @return encoded representation of a {@param node} - */ - virtual outcome::result encodeNode( - const Node &node, - StateVersion version, - const StoreChildren &store_children) const = 0; - - /** - * @brief Decode node from bytes - * @param encoded_data a buffer containing encoded representation of a node - * @return a node in the trie - */ - virtual outcome::result> decodeNode( - gsl::span encoded_data) const = 0; - - /** - * @brief Get the merkle value of a node - * @param buf byte representation of the node - * @return hash of \param buf or \param buf if it is shorter than the hash - */ - virtual common::Buffer merkleValue(const common::BufferView &buf) const = 0; - - /** - * @brief is this a hash of value, or value itself - */ - virtual bool isMerkleHash(const common::BufferView &buf) const = 0; - - /** - * @brief Get the hash of a node - * @param buf byte representation of the node - * @return hash of \param buf - */ - virtual common::Hash256 hash256(const common::BufferView &buf) const = 0; - }; - -} // namespace kagome::storage::trie - -#endif // KAGOME_TRIE_CODEC_HPP diff --git a/core/storage/trie/impl/ephemeral_trie_batch_impl.cpp b/core/storage/trie/impl/ephemeral_trie_batch_impl.cpp index 7622951737..0568ac4cc8 100644 --- a/core/storage/trie/impl/ephemeral_trie_batch_impl.cpp +++ b/core/storage/trie/impl/ephemeral_trie_batch_impl.cpp @@ -47,7 +47,7 @@ namespace kagome::storage::trie { return kEmptyRootHash; } - outcome::result> + outcome::result> EphemeralTrieBatchImpl::createFromTrieHash(const RootHash &trie_hash) { OUTCOME_TRY(trie, serializer_->retrieveTrie(trie_hash, nullptr)); return std::make_unique( diff --git a/core/storage/trie/impl/ephemeral_trie_batch_impl.hpp b/core/storage/trie/impl/ephemeral_trie_batch_impl.hpp index aceae0dcd5..743a5bf71c 100644 --- a/core/storage/trie/impl/ephemeral_trie_batch_impl.hpp +++ b/core/storage/trie/impl/ephemeral_trie_batch_impl.hpp @@ -8,8 +8,8 @@ #include "storage/trie/impl/trie_batch_base.hpp" -#include "storage/trie/codec.hpp" #include "storage/trie/polkadot_trie/polkadot_trie.hpp" +#include "storage/trie/serialization/codec.hpp" #include "storage/trie/serialization/trie_serializer.hpp" #include "storage/trie/trie_batches.hpp" @@ -32,7 +32,7 @@ namespace kagome::storage::trie { outcome::result commit(StateVersion version) override; protected: - virtual outcome::result> createFromTrieHash( + virtual outcome::result> createFromTrieHash( const RootHash &trie_hash) override; private: diff --git a/core/storage/trie/impl/persistent_trie_batch_impl.cpp b/core/storage/trie/impl/persistent_trie_batch_impl.cpp index 2ffb43da68..95ebcd527a 100644 --- a/core/storage/trie/impl/persistent_trie_batch_impl.cpp +++ b/core/storage/trie/impl/persistent_trie_batch_impl.cpp @@ -11,6 +11,7 @@ #include "storage/trie/polkadot_trie/polkadot_trie_cursor_impl.hpp" #include "storage/trie/polkadot_trie/trie_error.hpp" #include "storage/trie/serialization/trie_serializer.hpp" +#include "storage/trie_pruner/trie_pruner.hpp" OUTCOME_CPP_DEFINE_CATEGORY(kagome::storage::trie, PersistentTrieBatchImpl::Error, @@ -29,17 +30,21 @@ namespace kagome::storage::trie { std::shared_ptr codec, std::shared_ptr serializer, TrieChangesTrackerOpt changes, - std::shared_ptr trie) + std::shared_ptr trie, + std::shared_ptr state_pruner) : TrieBatchBase{std::move(codec), std::move(serializer), std::move(trie)}, - changes_{std::move(changes)} { + changes_{std::move(changes)}, + state_pruner_{std::move(state_pruner)} { BOOST_ASSERT((changes_.has_value() && changes_.value() != nullptr) or not changes_.has_value()); + BOOST_ASSERT(state_pruner_ != nullptr); } outcome::result PersistentTrieBatchImpl::commit( StateVersion version) { OUTCOME_TRY(commitChildren(version)); OUTCOME_TRY(root, serializer_->storeTrie(*trie_, version)); + OUTCOME_TRY(state_pruner_->addNewState(*trie_, version)); SL_TRACE_FUNC_CALL(logger_, root); return std::move(root); } @@ -80,11 +85,11 @@ namespace kagome::storage::trie { return outcome::success(); } - outcome::result> + outcome::result> PersistentTrieBatchImpl::createFromTrieHash(const RootHash &trie_hash) { OUTCOME_TRY(trie, serializer_->retrieveTrie(trie_hash, nullptr)); return std::make_unique( - codec_, serializer_, changes_, trie); + codec_, serializer_, changes_, trie, state_pruner_); } } // namespace kagome::storage::trie diff --git a/core/storage/trie/impl/persistent_trie_batch_impl.hpp b/core/storage/trie/impl/persistent_trie_batch_impl.hpp index 19cdda27eb..59669200b3 100644 --- a/core/storage/trie/impl/persistent_trie_batch_impl.hpp +++ b/core/storage/trie/impl/persistent_trie_batch_impl.hpp @@ -13,10 +13,14 @@ #include "log/logger.hpp" #include "primitives/event_types.hpp" #include "storage/changes_trie/changes_tracker.hpp" -#include "storage/trie/codec.hpp" +#include "storage/trie/serialization/codec.hpp" #include "storage/trie/serialization/trie_serializer.hpp" #include "storage/trie/trie_batches.hpp" +namespace kagome::storage::trie_pruner { + class TriePruner; +} + namespace kagome::storage::trie { class PersistentTrieBatchImpl final : public TrieBatchBase { @@ -25,11 +29,12 @@ namespace kagome::storage::trie { NO_TRIE = 1, }; - PersistentTrieBatchImpl(std::shared_ptr codec, - std::shared_ptr serializer, - TrieChangesTrackerOpt changes, - std::shared_ptr trie); - + PersistentTrieBatchImpl( + std::shared_ptr codec, + std::shared_ptr serializer, + TrieChangesTrackerOpt changes, + std::shared_ptr trie, + std::shared_ptr state_pruner); ~PersistentTrieBatchImpl() override = default; outcome::result commit(StateVersion version) override; @@ -42,11 +47,12 @@ namespace kagome::storage::trie { outcome::result remove(const BufferView &key) override; protected: - virtual outcome::result> createFromTrieHash( + virtual outcome::result> createFromTrieHash( const RootHash &trie_hash) override; private: TrieChangesTrackerOpt changes_; + std::shared_ptr state_pruner_; }; } // namespace kagome::storage::trie diff --git a/core/storage/trie/impl/topper_trie_batch_impl.cpp b/core/storage/trie/impl/topper_trie_batch_impl.cpp index 5719f3923a..e609e4b2ec 100644 --- a/core/storage/trie/impl/topper_trie_batch_impl.cpp +++ b/core/storage/trie/impl/topper_trie_batch_impl.cpp @@ -104,7 +104,7 @@ namespace kagome::storage::trie { outcome::result TopperTrieBatchImpl::put(const BufferView &key, BufferOrView &&value) { - cache_.insert_or_assign(Buffer{key}, value.into()); + cache_.insert_or_assign(Buffer{key}, value.intoBuffer()); return outcome::success(); } diff --git a/core/storage/trie/impl/trie_batch_base.cpp b/core/storage/trie/impl/trie_batch_base.cpp index dad392afaa..6a7bc5b104 100644 --- a/core/storage/trie/impl/trie_batch_base.cpp +++ b/core/storage/trie/impl/trie_batch_base.cpp @@ -8,6 +8,8 @@ #include "storage/trie/polkadot_trie/polkadot_trie.hpp" #include "storage/trie/polkadot_trie/polkadot_trie_cursor_impl.hpp" +#include + namespace kagome::storage::trie { TrieBatchBase::TrieBatchBase(std::shared_ptr codec, @@ -51,7 +53,7 @@ namespace kagome::storage::trie { : serializer_->getEmptyRootHash(); OUTCOME_TRY(unique_batch, createFromTrieHash(child_root_hash)); - auto batch = std::shared_ptr{std::move(unique_batch)}; + auto batch = std::shared_ptr{std::move(unique_batch)}; auto [it, success] = child_batches_.insert({path, batch}); if (success) { diff --git a/core/storage/trie/impl/trie_batch_base.hpp b/core/storage/trie/impl/trie_batch_base.hpp index 8519078b25..8ad821eaae 100644 --- a/core/storage/trie/impl/trie_batch_base.hpp +++ b/core/storage/trie/impl/trie_batch_base.hpp @@ -8,6 +8,9 @@ #include "storage/trie/trie_batches.hpp" +#include +#include + #include "log/logger.hpp" #include "storage/trie/serialization/trie_serializer.hpp" @@ -38,7 +41,7 @@ namespace kagome::storage::trie { createChildBatch(common::BufferView path) override; protected: - virtual outcome::result> createFromTrieHash( + virtual outcome::result> createFromTrieHash( const RootHash &trie_hash) = 0; outcome::result commitChildren(StateVersion version); @@ -50,7 +53,7 @@ namespace kagome::storage::trie { std::shared_ptr trie_; private: - std::unordered_map> + std::unordered_map> child_batches_; }; diff --git a/core/storage/trie/impl/trie_storage_impl.cpp b/core/storage/trie/impl/trie_storage_impl.cpp index a71ff8a140..f17a5d1469 100644 --- a/core/storage/trie/impl/trie_storage_impl.cpp +++ b/core/storage/trie/impl/trie_storage_impl.cpp @@ -17,30 +17,40 @@ namespace kagome::storage::trie { TrieStorageImpl::createEmpty( const std::shared_ptr &trie_factory, std::shared_ptr codec, - std::shared_ptr serializer) { + std::shared_ptr serializer, + std::shared_ptr state_pruner) { // will never be used, so content of the callback doesn't matter auto empty_trie = - trie_factory->createEmpty([](auto &) { return outcome::success(); }); + trie_factory->createEmpty(); // ensure retrieval of empty trie succeeds OUTCOME_TRY(serializer->storeTrie(*empty_trie, StateVersion::V0)); return std::unique_ptr( - new TrieStorageImpl(std::move(codec), std::move(serializer))); + new TrieStorageImpl(std::move(codec), + std::move(serializer), + std::move(state_pruner))); } outcome::result> TrieStorageImpl::createFromStorage( std::shared_ptr codec, - std::shared_ptr serializer) { + std::shared_ptr serializer, + std::shared_ptr state_pruner) { return std::unique_ptr( - new TrieStorageImpl(std::move(codec), std::move(serializer))); + new TrieStorageImpl(std::move(codec), + std::move(serializer), + std::move(state_pruner))); } - TrieStorageImpl::TrieStorageImpl(std::shared_ptr codec, - std::shared_ptr serializer) + TrieStorageImpl::TrieStorageImpl( + std::shared_ptr codec, + std::shared_ptr serializer, + std::shared_ptr state_pruner) : codec_{std::move(codec)}, serializer_{std::move(serializer)}, + state_pruner_{std::move(state_pruner)}, logger_{log::createLogger("TrieStorage", "storage")} { BOOST_ASSERT(codec_ != nullptr); + BOOST_ASSERT(state_pruner_ != nullptr); BOOST_ASSERT(serializer_ != nullptr); } @@ -50,15 +60,15 @@ namespace kagome::storage::trie { SL_DEBUG(logger_, "Initialize persistent trie batch with root: {}", root.toHex()); - OUTCOME_TRY(trie, serializer_->retrieveTrie(Buffer{root}, nullptr)); + OUTCOME_TRY(trie, serializer_->retrieveTrie(root, nullptr)); return std::make_unique( - codec_, serializer_, std::move(changes_tracker), std::move(trie)); + codec_, serializer_, changes_tracker, std::move(trie), state_pruner_); } outcome::result> TrieStorageImpl::getEphemeralBatchAt(const RootHash &root) const { SL_DEBUG(logger_, "Initialize ephemeral trie batch with root: {}", root); - OUTCOME_TRY(trie, serializer_->retrieveTrie(Buffer{root}, nullptr)); + OUTCOME_TRY(trie, serializer_->retrieveTrie(root, nullptr)); return std::make_unique( codec_, std::move(trie), serializer_, nullptr); } @@ -68,7 +78,7 @@ namespace kagome::storage::trie { const RootHash &root, const OnNodeLoaded &on_node_loaded) const { SL_DEBUG( logger_, "Initialize proof reading trie batch with root: {}", root); - OUTCOME_TRY(trie, serializer_->retrieveTrie(Buffer{root}, on_node_loaded)); + OUTCOME_TRY(trie, serializer_->retrieveTrie(root, on_node_loaded)); return std::make_unique( codec_, std::move(trie), serializer_, on_node_loaded); } diff --git a/core/storage/trie/impl/trie_storage_impl.hpp b/core/storage/trie/impl/trie_storage_impl.hpp index 243dfd6397..708e76f362 100644 --- a/core/storage/trie/impl/trie_storage_impl.hpp +++ b/core/storage/trie/impl/trie_storage_impl.hpp @@ -10,10 +10,14 @@ #include "log/logger.hpp" #include "primitives/event_types.hpp" -#include "storage/trie/codec.hpp" #include "storage/trie/polkadot_trie/polkadot_trie_factory.hpp" +#include "storage/trie/serialization/codec.hpp" #include "storage/trie/serialization/trie_serializer.hpp" +namespace kagome::storage::trie_pruner { + class TriePruner; +} + namespace kagome::storage::trie { class TrieStorageImpl : public TrieStorage { @@ -24,11 +28,13 @@ namespace kagome::storage::trie { static outcome::result> createEmpty( const std::shared_ptr &trie_factory, std::shared_ptr codec, - std::shared_ptr serializer); + std::shared_ptr serializer, + std::shared_ptr state_pruner); static outcome::result> createFromStorage( std::shared_ptr codec, - std::shared_ptr serializer); + std::shared_ptr serializer, + std::shared_ptr state_pruner); TrieStorageImpl(TrieStorageImpl const &) = delete; void operator=(const TrieStorageImpl &) = delete; @@ -46,12 +52,15 @@ namespace kagome::storage::trie { const OnNodeLoaded &on_node_loaded) const override; protected: - TrieStorageImpl(std::shared_ptr codec, - std::shared_ptr serializer); + TrieStorageImpl( + std::shared_ptr codec, + std::shared_ptr serializer, + std::shared_ptr state_pruner); private: std::shared_ptr codec_; std::shared_ptr serializer_; + std::shared_ptr state_pruner_; log::Logger logger_; }; diff --git a/core/storage/trie/node.hpp b/core/storage/trie/node.hpp deleted file mode 100644 index 37d638de04..0000000000 --- a/core/storage/trie/node.hpp +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef KAGOME_NODE_HPP -#define KAGOME_NODE_HPP - -namespace kagome::storage::trie { - - struct Node { - virtual ~Node() = default; - }; - -} // namespace kagome::storage::trie - -#endif // KAGOME_NODE_HPP diff --git a/core/storage/trie/polkadot_trie/polkadot_trie.hpp b/core/storage/trie/polkadot_trie/polkadot_trie.hpp index bfa22c19e3..216e4cb29c 100644 --- a/core/storage/trie/polkadot_trie/polkadot_trie.hpp +++ b/core/storage/trie/polkadot_trie/polkadot_trie.hpp @@ -9,6 +9,7 @@ #include "storage/buffer_map_types.hpp" #include "storage/trie/polkadot_trie/polkadot_trie_cursor.hpp" +#include "storage/trie/polkadot_trie/trie_error.hpp" #include "storage/trie/polkadot_trie/trie_node.hpp" namespace kagome::storage::trie { @@ -17,14 +18,47 @@ namespace kagome::storage::trie { * For specification see Polkadot Runtime Environment Protocol Specification * '2.1.2 The General Tree Structure' and further */ - class PolkadotTrie : public BufferStorage { + class PolkadotTrie : public BufferStorage, + public std::enable_shared_from_this { public: using NodePtr = std::shared_ptr; using ConstNodePtr = std::shared_ptr; using BranchPtr = std::shared_ptr; using ConstBranchPtr = std::shared_ptr; - using NodeRetrieveFunctor = std::function( - std::shared_ptr const &)>; + + using NodeRetrieveFunction = std::function( + const std::shared_ptr &)>; + using ValueRetrieveFunction = + std::function>( + const common::Hash256 & /* value hash */)>; + + struct RetrieveFunctions { + RetrieveFunctions() + : retrieve_node{defaultNodeRetrieve}, + retrieve_value{defaultValueRetrieve} {} + + RetrieveFunctions(NodeRetrieveFunction retrieve_node, + ValueRetrieveFunction retrieve_value) + : retrieve_node{std::move(retrieve_node)}, + retrieve_value{std::move(retrieve_value)} {} + + inline static outcome::result defaultNodeRetrieve( + const std::shared_ptr &node) { + BOOST_ASSERT_MSG( + node == nullptr + or std::dynamic_pointer_cast(node) != nullptr, + "Unexpected Dummy node."); + return std::dynamic_pointer_cast(node); + } + + inline static outcome::result> + defaultValueRetrieve(const common::Hash256 &) { + return TrieError::VALUE_RETRIEVE_NOT_PROVIDED; + } + + NodeRetrieveFunction retrieve_node; + ValueRetrieveFunction retrieve_value; + }; /** * This callback is called when a node is detached from a trie. It is called @@ -76,6 +110,8 @@ namespace kagome::storage::trie { virtual outcome::result getNode( ConstNodePtr parent, const NibblesView &key_nibbles) const = 0; + using BranchVisitor = std::function( + const BranchNode &, uint8_t idx, const TrieNode &child)>; /** * Invokes callback on each node starting from \arg parent and ending on the * node corresponding to the \arg path @@ -84,23 +120,13 @@ namespace kagome::storage::trie { virtual outcome::result forNodeInPath( ConstNodePtr parent, const NibblesView &path, - const std::function( - BranchNode const &, uint8_t idx)> &callback) const = 0; + const BranchVisitor &callback) const = 0; - virtual std::unique_ptr trieCursor() = 0; + virtual std::unique_ptr trieCursor() const = 0; std::unique_ptr cursor() final { return trieCursor(); } - - inline static outcome::result defaultNodeRetrieveFunctor( - const std::shared_ptr &node) { - BOOST_ASSERT_MSG( - node == nullptr - or std::dynamic_pointer_cast(node) != nullptr, - "Unexpected Dummy node."); - return std::dynamic_pointer_cast(node); - } }; } // namespace kagome::storage::trie diff --git a/core/storage/trie/polkadot_trie/polkadot_trie_cursor_impl.cpp b/core/storage/trie/polkadot_trie/polkadot_trie_cursor_impl.cpp index 77625a41de..e3693db26c 100644 --- a/core/storage/trie/polkadot_trie/polkadot_trie_cursor_impl.cpp +++ b/core/storage/trie/polkadot_trie/polkadot_trie_cursor_impl.cpp @@ -35,7 +35,9 @@ namespace kagome::storage::trie { PolkadotTrieCursorImpl::SearchState::visitChild(uint8_t index, const TrieNode &child) { auto *current_as_branch = dynamic_cast(current_); - if (current_as_branch == nullptr) return Error::INVALID_NODE_TYPE; + if (current_as_branch == nullptr) { + return Error::INVALID_NODE_TYPE; + } path_.emplace_back(*current_as_branch, index); current_ = &child; return outcome::success(); @@ -86,7 +88,7 @@ namespace kagome::storage::trie { auto ¤t = std::get(state_).getCurrent(); // while there is a node in a trie with the given key, it contains no value, // thus cannot be pointed at by the cursor - if (not current.value) { + if (not current.getValue()) { state_ = InvalidState{Error::KEY_NOT_FOUND}; return false; } @@ -125,11 +127,11 @@ namespace kagome::storage::trie { auto [sought_nibbles_mismatch, current_mismatch] = std::mismatch(sought_nibbles.begin(), sought_nibbles.end(), - current.key_nibbles.begin(), - current.key_nibbles.end()); + current.getKeyNibbles().begin(), + current.getKeyNibbles().end()); // one nibble sequence is a prefix of the other bool sought_is_prefix = sought_nibbles_mismatch == sought_nibbles.end(); - bool current_is_prefix = current_mismatch == current.key_nibbles.end(); + bool current_is_prefix = current_mismatch == current.getKeyNibbles().end(); // if sought nibbles are lexicographically less or equal to the current // nibbles, we just take the closest node with value @@ -140,7 +142,7 @@ namespace kagome::storage::trie { "The sought key '{}' is {} than current '{}'", common::hex_lower(sought_nibbles), sought_less_or_eq ? "less or eq" : "greater", - common::hex_lower(current.key_nibbles)); + common::hex_lower(current.getKeyNibbles())); if (sought_less_or_eq) { if (current.isBranch()) { SL_TRACE(log_, "We're in a branch and search next node in subtree"); @@ -232,7 +234,7 @@ namespace kagome::storage::trie { outcome::result PolkadotTrieCursorImpl::nextNodeWithValueInSubTree( const TrieNode &parent) { auto *current = &parent; - while (not current->value) { + while (not current->getValue()) { if (not current->isBranch()) { return Error::INVALID_NODE_TYPE; } @@ -294,7 +296,7 @@ namespace kagome::storage::trie { if (std::holds_alternative(state_)) { state_ = SearchState{*trie_->getRoot()}; - if (trie_->getRoot()->value) { + if (trie_->getRoot()->getValue()) { return outcome::success(); } } @@ -335,12 +337,12 @@ namespace kagome::storage::trie { for (const auto &node_idx : search_state.getPath()) { const auto &node = node_idx.parent; auto idx = node_idx.child_idx; - std::copy(node.key_nibbles.begin(), - node.key_nibbles.end(), + std::copy(node.getKeyNibbles().begin(), + node.getKeyNibbles().end(), std::back_inserter(key_nibbles)); key_nibbles.putUint8(idx); } - key_nibbles.put(search_state.getCurrent().key_nibbles); + key_nibbles.put(search_state.getCurrent().getKeyNibbles()); return key_nibbles.toByteBuffer(); } @@ -355,7 +357,7 @@ namespace kagome::storage::trie { std::optional PolkadotTrieCursorImpl::value() const { if (const auto *search_state = std::get_if(&state_); search_state != nullptr) { - const auto &value_opt = search_state->getCurrent().value; + const auto &value_opt = search_state->getCurrent().getValue(); if (value_opt) { // TODO(turuslan): #1470, return error if (auto r = @@ -381,12 +383,12 @@ namespace kagome::storage::trie { } SearchState search_state{*trie_->getRoot()}; - auto add_visited_child = [&search_state, this]( + auto add_visited_child = [&search_state]( auto &branch, - auto idx) mutable -> outcome::result { - OUTCOME_TRY(child, trie_->retrieveChild(branch, idx)); + auto idx, + auto &child) mutable -> outcome::result { BOOST_VERIFY( // NOLINT(bugprone-lambda-function-name) - search_state.visitChild(idx, *child)); + search_state.visitChild(idx, child)); return outcome::success(); }; auto res = trie_->forNodeInPath( diff --git a/core/storage/trie/polkadot_trie/polkadot_trie_factory.hpp b/core/storage/trie/polkadot_trie/polkadot_trie_factory.hpp index 62d9bda4ac..839072d797 100644 --- a/core/storage/trie/polkadot_trie/polkadot_trie_factory.hpp +++ b/core/storage/trie/polkadot_trie/polkadot_trie_factory.hpp @@ -17,9 +17,8 @@ namespace kagome::storage::trie { * @param f functor that a trie uses to obtain a child of a branch. If * optional is none, the default one will be used */ - virtual std::unique_ptr createEmpty( - PolkadotTrie::NodeRetrieveFunctor f = - PolkadotTrie::defaultNodeRetrieveFunctor) const = 0; + virtual std::shared_ptr createEmpty( + PolkadotTrie::RetrieveFunctions retrieve_functions = {}) const = 0; /** * Creates a trie with the given root @@ -28,8 +27,7 @@ namespace kagome::storage::trie { */ virtual std::shared_ptr createFromRoot( PolkadotTrie::NodePtr root, - PolkadotTrie::NodeRetrieveFunctor f = - PolkadotTrie::defaultNodeRetrieveFunctor) const = 0; + PolkadotTrie::RetrieveFunctions retrieve_functions = {}) const = 0; virtual ~PolkadotTrieFactory() = default; }; diff --git a/core/storage/trie/polkadot_trie/polkadot_trie_factory_impl.cpp b/core/storage/trie/polkadot_trie/polkadot_trie_factory_impl.cpp index 7e6cd25c43..fd995f3cb3 100644 --- a/core/storage/trie/polkadot_trie/polkadot_trie_factory_impl.cpp +++ b/core/storage/trie/polkadot_trie/polkadot_trie_factory_impl.cpp @@ -7,14 +7,16 @@ namespace kagome::storage::trie { - std::unique_ptr PolkadotTrieFactoryImpl::createEmpty( - PolkadotTrie::NodeRetrieveFunctor f) const { - return std::make_unique(f); + std::shared_ptr PolkadotTrieFactoryImpl::createEmpty( + PolkadotTrie::RetrieveFunctions retrieve_functions) const { + return PolkadotTrieImpl::createEmpty(std::move(retrieve_functions)); } std::shared_ptr PolkadotTrieFactoryImpl::createFromRoot( - PolkadotTrie::NodePtr root, PolkadotTrie::NodeRetrieveFunctor f) const { - return std::make_shared(std::move(root), std::move(f)); + PolkadotTrie::NodePtr root, + PolkadotTrie::RetrieveFunctions retrieve_functions) const { + return PolkadotTrieImpl::create( + std::move(root), std::move(retrieve_functions)); } } // namespace kagome::storage::trie diff --git a/core/storage/trie/polkadot_trie/polkadot_trie_factory_impl.hpp b/core/storage/trie/polkadot_trie/polkadot_trie_factory_impl.hpp index 834e9e9770..ad3526b968 100644 --- a/core/storage/trie/polkadot_trie/polkadot_trie_factory_impl.hpp +++ b/core/storage/trie/polkadot_trie/polkadot_trie_factory_impl.hpp @@ -13,11 +13,12 @@ namespace kagome::storage::trie { class PolkadotTrieFactoryImpl : public PolkadotTrieFactory { public: - std::unique_ptr createEmpty( - PolkadotTrie::NodeRetrieveFunctor f) const override; + std::shared_ptr createEmpty( + PolkadotTrie::RetrieveFunctions retrieve_functions) const override; + std::shared_ptr createFromRoot( PolkadotTrie::NodePtr root, - PolkadotTrie::NodeRetrieveFunctor f) const override; + PolkadotTrie::RetrieveFunctions retrieve_functions) const override; }; } // namespace kagome::storage::trie diff --git a/core/storage/trie/polkadot_trie/polkadot_trie_impl.cpp b/core/storage/trie/polkadot_trie/polkadot_trie_impl.cpp index 6cf0d43aa5..dfe7fc1f77 100644 --- a/core/storage/trie/polkadot_trie/polkadot_trie_impl.cpp +++ b/core/storage/trie/polkadot_trie/polkadot_trie_impl.cpp @@ -26,16 +26,20 @@ namespace kagome::storage::trie { class OpaqueNodeStorage final { public: - OpaqueNodeStorage(PolkadotTrie::NodeRetrieveFunctor node_retriever, + OpaqueNodeStorage(PolkadotTrie::NodeRetrieveFunction node_retriever, + PolkadotTrie::ValueRetrieveFunction value_retriever, std::shared_ptr root) noexcept - : retrieve_node_{std::move(node_retriever)}, root_{std::move(root)} {} + : retrieve_node_{std::move(node_retriever)}, + retrieve_value_{std::move(value_retriever)}, + root_{std::move(root)} {} static outcome::result> createAt( std::shared_ptr root, - PolkadotTrie::NodeRetrieveFunctor node_retriever) { + PolkadotTrie::NodeRetrieveFunction node_retriever, + PolkadotTrie::ValueRetrieveFunction value_retriever) { OUTCOME_TRY(root_node, node_retriever(root)); return std::unique_ptr{ - new OpaqueNodeStorage{node_retriever, root_node}}; + new OpaqueNodeStorage{node_retriever, value_retriever, root_node}}; } [[nodiscard]] const std::shared_ptr &getRoot() { @@ -51,7 +55,7 @@ namespace kagome::storage::trie { } [[nodiscard]] outcome::result> getChild( - BranchNode const &parent, uint8_t idx) const { + const BranchNode &parent, uint8_t idx) const { // SAFETY: changing a parent's opaque child node from a handle to a node // to the actual node doesn't break it's const correctness, because opaque // nodes are meant to hide their content @@ -63,7 +67,7 @@ namespace kagome::storage::trie { } [[nodiscard]] outcome::result> getChild( - BranchNode const &parent, uint8_t idx) { + const BranchNode &parent, uint8_t idx) { // SAFETY: adding constness, not removing auto const_this = const_cast(this); OUTCOME_TRY(const_child, const_this->getChild(parent, idx)); @@ -72,7 +76,8 @@ namespace kagome::storage::trie { return child; } - PolkadotTrie::NodeRetrieveFunctor retrieve_node_; + PolkadotTrie::NodeRetrieveFunction retrieve_node_; + PolkadotTrie::ValueRetrieveFunction retrieve_value_; std::shared_ptr root_; }; } // namespace kagome::storage::trie @@ -99,37 +104,43 @@ namespace { const kagome::log::Logger &logger, PolkadotTrie::NodePtr &parent, OpaqueNodeStorage &node_storage) { - if (!parent->isBranch()) return outcome::success(); + if (!parent->isBranch()) { + return outcome::success(); + } auto &branch = dynamic_cast(*parent); auto bitmap = branch.childrenBitmap(); if (bitmap == 0) { - if (parent->value) { + if (parent->getValue()) { // turn branch node left with no children to a leaf node - parent = std::make_shared(parent->key_nibbles, parent->value); + parent = std::make_shared(parent->getKeyNibbles(), + parent->getValue()); SL_TRACE(logger, "handleDeletion: turn childless branch into a leaf"); } else { // this case actual only for clearPrefix, unreal situation in deletion parent = nullptr; SL_TRACE(logger, "handleDeletion: nullify valueless branch parent"); } - } else if (branch.childrenNum() == 1 && !branch.value) { + } else if (branch.childrenNum() == 1 && !branch.getValue()) { size_t idx = 0; - while (bitmap >>= 1u) ++idx; + while (bitmap >>= 1u) { + ++idx; + } OUTCOME_TRY(child, node_storage.getChild(branch, idx)); if (!child->isBranch()) { - parent = std::make_shared(parent->key_nibbles, child->value); + parent = std::make_shared(parent->getKeyNibbles(), + child->getValue()); SL_TRACE(logger, "handleDeletion: turn a branch with single leaf child into " "its child"); } else { branch.children = dynamic_cast(*child).children; - parent->value = child->value; + parent->setValue(child->getValue()); SL_TRACE(logger, "handleDeletion: turn a branch with single branch child into " "its child"); } - parent->key_nibbles.putUint8(idx).put(child->key_nibbles); + parent->getMutKeyNibbles().putUint8(idx).put(child->getKeyNibbles()); } return outcome::success(); } @@ -147,16 +158,19 @@ namespace { } SL_TRACE(logger, "deleteNode: currently in {}, sought key is {}", - node->key_nibbles.toHex(), + node->getKeyNibbles().toHex(), sought_key); if (node->isBranch()) { auto &branch = dynamic_cast(*node); - if (node->key_nibbles == sought_key) { + if (node->getKeyNibbles() == sought_key) { SL_TRACE(logger, "deleteNode: deleting value in branch; stop"); - node->value = ValueAndHash{}; + node->setValue(ValueAndHash{}); } else { - auto length = getCommonPrefixLength(node->key_nibbles, sought_key); + auto length = getCommonPrefixLength(node->getKeyNibbles(), sought_key); + if (length >= sought_key.size()) { + return outcome::success(); + } OUTCOME_TRY(child, node_storage.getChild(branch, sought_key[length])); SL_TRACE( logger, "deleteNode: go to child {:x}", (int)sought_key[length]); @@ -165,7 +179,7 @@ namespace { branch.children[sought_key[length]] = child; } OUTCOME_TRY(handleDeletion(logger, node, node_storage)); - } else if (node->key_nibbles == sought_key) { + } else if (node->getKeyNibbles() == sought_key) { SL_TRACE(logger, "deleteNode: nullifying leaf node; stop"); node = nullptr; } @@ -175,8 +189,8 @@ namespace { outcome::result notifyOnDetached( PolkadotTrie::NodePtr &node, const PolkadotTrie::OnDetachCallback &callback) { - auto key = node->key_nibbles.toByteBuffer(); - OUTCOME_TRY(callback(key, std::move(node->value.value))); + auto key = node->getKeyNibbles().toByteBuffer(); + OUTCOME_TRY(callback(key, std::move(node->getMutableValue().value))); return outcome::success(); } @@ -205,11 +219,11 @@ namespace { return outcome::success(); } - if (std::greater_equal()(parent->key_nibbles.size(), + if (std::greater_equal()(parent->getKeyNibbles().size(), prefix.size())) { // if this is the node to be detached -- detach it if (std::equal( - prefix.begin(), prefix.end(), parent->key_nibbles.begin())) { + prefix.begin(), prefix.end(), parent->getKeyNibbles().begin())) { // remove all children one by one according to limit if (parent->isBranch()) { auto &branch = dynamic_cast(*parent); @@ -231,14 +245,14 @@ namespace { } } if (not limit or count < limit.value()) { - if (parent->value) { - OUTCOME_TRY(trie.retrieveValue(parent->value)); + if (parent->getValue()) { + OUTCOME_TRY(trie.retrieveValue(parent->getMutableValue())); OUTCOME_TRY(notifyOnDetached(parent, callback)); ++count; } parent = nullptr; } else { - if (parent->value) { + if (parent->getValue()) { // we saw a value after limit, so not finished finished = false; } @@ -253,14 +267,14 @@ namespace { // if parent's key is smaller, and it is not a prefix of the prefix, don't // change anything - if (not std::equal(parent->key_nibbles.begin(), - parent->key_nibbles.end(), + if (not std::equal(parent->getKeyNibbles().begin(), + parent->getKeyNibbles().end(), prefix.begin())) { return outcome::success(); } if (parent->isBranch()) { - const auto length = parent->key_nibbles.size(); + const auto length = parent->getKeyNibbles().size(); auto &branch = dynamic_cast(*parent); auto &child = branch.children.at(prefix[length]); if (child != nullptr) { @@ -285,13 +299,33 @@ namespace { namespace kagome::storage::trie { PolkadotTrieImpl::PolkadotTrieImpl(PolkadotTrieImpl &&) = default; + PolkadotTrieImpl &PolkadotTrieImpl::operator=(PolkadotTrieImpl &&) = default; + + std::shared_ptr PolkadotTrieImpl::createEmpty( + RetrieveFunctions retrieve_functions) { + return std::shared_ptr( + new PolkadotTrieImpl{std::move(retrieve_functions)}); + } + + std::shared_ptr PolkadotTrieImpl::create( + NodePtr root, RetrieveFunctions retrieve_functions) { + return std::shared_ptr( + new PolkadotTrieImpl{root, std::move(retrieve_functions)}); + } - PolkadotTrieImpl::PolkadotTrieImpl(NodeRetrieveFunctor f) - : nodes_{std::make_unique(std::move(f), nullptr)}, + PolkadotTrieImpl::PolkadotTrieImpl(RetrieveFunctions retrieve_functions) + : nodes_{std::make_unique( + std::move(retrieve_functions.retrieve_node), + std::move(retrieve_functions.retrieve_value), + nullptr)}, logger_{log::createLogger("PolkadotTrie", "trie")} {} - PolkadotTrieImpl::PolkadotTrieImpl(NodePtr root, NodeRetrieveFunctor f) - : nodes_{std::make_unique(std::move(f), root)}, + PolkadotTrieImpl::PolkadotTrieImpl(NodePtr root, + RetrieveFunctions retrieve_functions) + : nodes_{std::make_unique( + std::move(retrieve_functions.retrieve_node), + std::move(retrieve_functions.retrieve_value), + root)}, logger_{log::createLogger("PolkadotTrie", "trie")} {} PolkadotTrieImpl::~PolkadotTrieImpl() {} @@ -315,7 +349,7 @@ namespace kagome::storage::trie { // will be written back to the storage only on storeNode call OUTCOME_TRY( n, - insert(root, k_enc, std::make_shared(k_enc, value.into()))); + insert(root, k_enc, std::make_shared(k_enc, value.intoBuffer()))); nodes_->setRoot(n); return outcome::success(); @@ -346,7 +380,7 @@ namespace kagome::storage::trie { const NodePtr &parent, const NibblesView &key_nibbles, NodePtr node) { // just update the node key and return it as the new root if (parent == nullptr) { - node->key_nibbles = key_nibbles; + node->setKeyNibbles(key_nibbles); return node; } @@ -356,42 +390,43 @@ namespace kagome::storage::trie { } // need to convert this leaf into a branch auto br = std::make_shared(); - auto length = getCommonPrefixLength(key_nibbles, parent->key_nibbles); + auto length = getCommonPrefixLength(key_nibbles, parent->getKeyNibbles()); - if (parent->key_nibbles == key_nibbles && key_nibbles.size() == length) { - node->key_nibbles = key_nibbles; + if (parent->getKeyNibbles() == key_nibbles + && key_nibbles.size() == length) { + node->setKeyNibbles(key_nibbles); return node; } - br->key_nibbles = KeyNibbles{key_nibbles.subspan(0, length)}; - auto parentKey = parent->key_nibbles; + br->setKeyNibbles(KeyNibbles{key_nibbles.subspan(0, length)}); + auto parentKey = parent->getKeyNibbles(); // value goes at this branch if (key_nibbles.size() == length) { - br->value = node->value; + br->setValue(node->getValue()); // if we are not replacing previous leaf, then add it as a // child to the new branch - if (static_cast(parent->key_nibbles.size()) + if (static_cast(parent->getKeyNibbles().size()) > key_nibbles.size()) { - parent->key_nibbles = parent->key_nibbles.subbuffer(length + 1); + parent->setKeyNibbles(parent->getKeyNibbles().subbuffer(length + 1)); br->children.at(parentKey[length]) = parent; } return br; } - node->key_nibbles = KeyNibbles{key_nibbles.subspan(length + 1)}; + node->setKeyNibbles(KeyNibbles{key_nibbles.subspan(length + 1)}); - if (length == parent->key_nibbles.size()) { + if (length == parent->getKeyNibbles().size()) { // if leaf's key is covered by this branch, then make the leaf's // value the value at this branch - br->value = parent->value; + br->setValue(parent->getValue()); br->children.at(key_nibbles[length]) = node; } else { // otherwise, make the leaf a child of the branch and update its // partial key - parent->key_nibbles = parent->key_nibbles.subbuffer(length + 1); + parent->setKeyNibbles(parent->getKeyNibbles().subbuffer(length + 1)); br->children.at(parentKey[length]) = parent; br->children.at(key_nibbles[length]) = node; } @@ -401,12 +436,12 @@ namespace kagome::storage::trie { outcome::result PolkadotTrieImpl::updateBranch( BranchPtr parent, const NibblesView &key_nibbles, const NodePtr &node) { - auto length = getCommonPrefixLength(key_nibbles, parent->key_nibbles); + auto length = getCommonPrefixLength(key_nibbles, parent->getKeyNibbles()); - if (length == parent->key_nibbles.size()) { + if (length == parent->getKeyNibbles().size()) { // just set the value in the parent to the node value - if (key_nibbles == parent->key_nibbles) { - parent->value = node->value; + if (key_nibbles == parent->getKeyNibbles()) { + parent->setValue(node->getValue()); return parent; } OUTCOME_TRY(child, retrieveChild(*parent, key_nibbles[length])); @@ -415,19 +450,19 @@ namespace kagome::storage::trie { parent->children.at(key_nibbles[length]) = n; return parent; } - node->key_nibbles = KeyNibbles{key_nibbles.subspan(length + 1)}; + node->setKeyNibbles(KeyNibbles{key_nibbles.subspan(length + 1)}); parent->children.at(key_nibbles[length]) = node; return parent; } auto br = std::make_shared( KeyNibbles{key_nibbles.subspan(0, length)}); - auto parentIdx = parent->key_nibbles[length]; + auto parentIdx = parent->getKeyNibbles()[length]; OUTCOME_TRY( new_branch, - insert(nullptr, parent->key_nibbles.subspan(length + 1), parent)); + insert(nullptr, parent->getKeyNibbles().subspan(length + 1), parent)); br->children.at(parentIdx) = new_branch; if (key_nibbles.size() <= length) { - br->value = node->value; + br->setValue(node->getValue()); } else { OUTCOME_TRY(new_child, insert(nullptr, key_nibbles.subspan(length + 1), node)); @@ -452,9 +487,9 @@ namespace kagome::storage::trie { } auto nibbles = KeyNibbles::fromByteBuffer(key); OUTCOME_TRY(node, getNode(nodes_->getRoot(), nibbles)); - if (node && node->value) { - OUTCOME_TRY(retrieveValue(const_cast(node->value))); - return BufferView{*node->value.value}; + if (node && node->getValue()) { + OUTCOME_TRY(retrieveValue(const_cast(node->getValue()))); + return BufferView{*node->getValue().value}; } return std::nullopt; } @@ -478,19 +513,19 @@ namespace kagome::storage::trie { } if (current->isBranch()) { - if (current->key_nibbles == nibbles or nibbles.empty()) { + if (current->getKeyNibbles() == nibbles or nibbles.empty()) { return current; } - if (nibbles.size() < static_cast(current->key_nibbles.size())) { + if (nibbles.size() < static_cast(current->getKeyNibbles().size())) { return nullptr; } auto parent_as_branch = std::dynamic_pointer_cast(current); - auto length = getCommonPrefixLength(current->key_nibbles, nibbles); + auto length = getCommonPrefixLength(current->getKeyNibbles(), nibbles); OUTCOME_TRY(n, retrieveChild(*parent_as_branch, nibbles[length])); return getNode(n, nibbles.subspan(length + 1)); } - if (current->key_nibbles == nibbles) { + if (current->getKeyNibbles() == nibbles) { return current; } return nullptr; @@ -499,39 +534,39 @@ namespace kagome::storage::trie { outcome::result PolkadotTrieImpl::forNodeInPath( ConstNodePtr parent, const NibblesView &path, - const std::function(BranchNode const &, - uint8_t idx)> &callback) const { + const BranchVisitor &callback) const { if (parent == nullptr) { return TrieError::NO_VALUE; } if (parent->isBranch()) { // path is completely covered by the parent key - if (parent->key_nibbles == path or path.empty()) { + if (parent->getKeyNibbles() == path or path.empty()) { return outcome::success(); } - auto common_length = getCommonPrefixLength(parent->key_nibbles, path); + auto common_length = getCommonPrefixLength(parent->getKeyNibbles(), path); auto common_nibbles = - gsl::make_span(parent->key_nibbles.data(), common_length); + gsl::make_span(parent->getKeyNibbles().data(), common_length); // path is even less than the parent key (path is the prefix of the // parent key) if (path == common_nibbles - and path.size() < static_cast(parent->key_nibbles.size())) { + and path.size() + < static_cast(parent->getKeyNibbles().size())) { return outcome::success(); } auto parent_as_branch = std::dynamic_pointer_cast(parent); OUTCOME_TRY(child, retrieveChild(*parent_as_branch, path[common_length])); - OUTCOME_TRY(callback(*parent_as_branch, path[common_length])); + OUTCOME_TRY(callback(*parent_as_branch, path[common_length], *child)); return forNodeInPath(child, path.subspan(common_length + 1), callback); } - if (parent->key_nibbles == path) { + if (parent->getKeyNibbles() == path) { return outcome::success(); } return TrieError::NO_VALUE; } - std::unique_ptr PolkadotTrieImpl::trieCursor() { + std::unique_ptr PolkadotTrieImpl::trieCursor() const { return std::make_unique(shared_from_this()); } @@ -543,7 +578,7 @@ namespace kagome::storage::trie { OUTCOME_TRY(node, getNode(nodes_->getRoot(), KeyNibbles::fromByteBuffer(key))); - return node != nullptr && node->value; + return node != nullptr && node->getValue(); } bool PolkadotTrieImpl::empty() const { @@ -577,7 +612,8 @@ namespace kagome::storage::trie { outcome::result PolkadotTrieImpl::retrieveValue( ValueAndHash &value) const { if (value.hash && !value.value) { - OUTCOME_TRY(nodes_->retrieve_node_(std::make_shared(value))); + OUTCOME_TRY(loaded_value, nodes_->retrieve_value_(*value.hash)); + value.value = std::move(loaded_value); } return outcome::success(); } diff --git a/core/storage/trie/polkadot_trie/polkadot_trie_impl.hpp b/core/storage/trie/polkadot_trie/polkadot_trie_impl.hpp index 22a14a55d5..922e85ebe6 100644 --- a/core/storage/trie/polkadot_trie/polkadot_trie_impl.hpp +++ b/core/storage/trie/polkadot_trie/polkadot_trie_impl.hpp @@ -16,26 +16,39 @@ namespace kagome::storage::trie { class OpaqueNodeStorage; - class PolkadotTrieImpl final - : public PolkadotTrie, - public std::enable_shared_from_this { + class PolkadotTrieImpl final : public PolkadotTrie { public: enum class Error { INVALID_NODE_TYPE = 1 }; PolkadotTrieImpl(PolkadotTrieImpl &&); + PolkadotTrieImpl &operator=(PolkadotTrieImpl &&); + + PolkadotTrieImpl(const PolkadotTrieImpl &) = delete; + PolkadotTrieImpl &operator=(const PolkadotTrieImpl &) = delete; /** - * Creates an empty Trie + * Creates an empty Trie. * @param f a functor that will be used to obtain a child of a branch node * by its index. Most useful if Trie grows too big to occupy main memory and * is stored on an external storage + * @note since creation of a trie cursor uses shared_from_this, should be a + * shared_ptr */ - explicit PolkadotTrieImpl(PolkadotTrie::NodeRetrieveFunctor f = - PolkadotTrie::defaultNodeRetrieveFunctor); + static std::shared_ptr createEmpty( + RetrieveFunctions retrieve_functions = {}); + + /** + * Creates a Trie from the given root. + * @param f a functor that will be used to obtain a child of a branch node + * by its index. Most useful if Trie grows too big to occupy main memory and + * is stored on an external storage + * @note since creation of a trie cursor uses shared_from_this, should be a + * shared_ptr + */ + static std::shared_ptr create( + NodePtr root, + RetrieveFunctions retrieve_functions = {}); - explicit PolkadotTrieImpl(NodePtr root, - PolkadotTrie::NodeRetrieveFunctor f = - PolkadotTrie::defaultNodeRetrieveFunctor); ~PolkadotTrieImpl(); NodePtr getRoot() override; @@ -49,8 +62,7 @@ namespace kagome::storage::trie { outcome::result forNodeInPath( ConstNodePtr parent, const NibblesView &path, - const std::function( - BranchNode const &, uint8_t idx)> &callback) const override; + const BranchVisitor &callback) const override; /** * Remove all entries, which key starts with the prefix @@ -71,7 +83,7 @@ namespace kagome::storage::trie { outcome::result> tryGet( const common::BufferView &key) const override; - std::unique_ptr trieCursor() override; + std::unique_ptr trieCursor() const override; outcome::result contains( const common::BufferView &key) const override; @@ -86,6 +98,11 @@ namespace kagome::storage::trie { outcome::result retrieveValue(ValueAndHash &value) const override; private: + explicit PolkadotTrieImpl(RetrieveFunctions retrieve_functions); + + explicit PolkadotTrieImpl(NodePtr root, + RetrieveFunctions retrieve_functions); + outcome::result insert(const NodePtr &parent, const NibblesView &key_nibbles, NodePtr node); diff --git a/core/storage/trie/polkadot_trie/trie_error.cpp b/core/storage/trie/polkadot_trie/trie_error.cpp index 77b88dc557..b914f528fb 100644 --- a/core/storage/trie/polkadot_trie/trie_error.cpp +++ b/core/storage/trie/polkadot_trie/trie_error.cpp @@ -10,6 +10,9 @@ OUTCOME_CPP_DEFINE_CATEGORY(kagome::storage::trie, TrieError, e) { switch (e) { case TrieError::NO_VALUE: return "no stored value found by the given key"; + case TrieError::VALUE_RETRIEVE_NOT_PROVIDED: + return "attempt to retrieve a value by hash with no corresponding " + "callback provided"; } return "unknown"; } diff --git a/core/storage/trie/polkadot_trie/trie_error.hpp b/core/storage/trie/polkadot_trie/trie_error.hpp index f9f898cf94..aba8c16d16 100644 --- a/core/storage/trie/polkadot_trie/trie_error.hpp +++ b/core/storage/trie/polkadot_trie/trie_error.hpp @@ -13,7 +13,9 @@ namespace kagome::storage::trie { * @brief TrieDbError enum provides error codes for TrieDb methods */ enum class TrieError { - NO_VALUE = 1, // no stored value found by the given key + NO_VALUE = 1, // no stored value found by the given key + VALUE_RETRIEVE_NOT_PROVIDED, // attempt to retrieve a value by hash with no + // corresponding callback provided }; } // namespace kagome::storage::trie diff --git a/core/storage/trie/polkadot_trie/trie_node.hpp b/core/storage/trie/polkadot_trie/trie_node.hpp index e3aa7db1ea..d8e35855bd 100644 --- a/core/storage/trie/polkadot_trie/trie_node.hpp +++ b/core/storage/trie/polkadot_trie/trie_node.hpp @@ -12,7 +12,6 @@ #include "common/blob.hpp" #include "common/buffer.hpp" -#include "storage/trie/node.hpp" namespace kagome::storage::trie { @@ -80,15 +79,68 @@ namespace kagome::storage::trie { } }; + using MerkleHash = common::Hash256; + + struct MerkleValue final { + public: + // the only error is zero size or exceeding size limit, so returns an + // optional + static std::optional create(common::BufferView merkle_value) { + auto size = static_cast(merkle_value.size()); + if (size == common::Hash256::size()) { + return MerkleValue{common::Hash256::fromSpan(merkle_value).value(), + size}; + } else if (size < common::Hash256::size() && size > 0) { + common::Hash256 hash; + std::copy_n(merkle_value.begin(), size, hash.begin()); + return MerkleValue{hash, size}; + } + return std::nullopt; + } + + MerkleValue(const MerkleHash &hash) + : value{hash}, size{MerkleHash::size()} {} + + bool isHash() const { + return size == MerkleHash::size(); + } + + std::optional asHash() const { + if (isHash()) { + return value; + } + return std::nullopt; + } + + common::BufferView asBuffer() const { + return common::BufferView{value.begin(), value.begin() + size}; + } + + private: + MerkleValue(const common::Hash256 &value, size_t size) + : value{value}, size{size} {} + common::Hash256 value; + size_t size; + }; + class ValueAndHash { public: ValueAndHash() = default; - ValueAndHash(std::optional hash, - std::optional value, + ValueAndHash(std::optional value, + std::optional hash, bool dirty = true) : hash{hash}, value{std::move(value)}, dirty_{dirty} {} + operator bool() const { - return hash || value; + return is_some(); + } + + bool is_none() const { + return !is_some(); + } + + bool is_some() const { + return hash.has_value() || value.has_value(); } bool dirty() const { @@ -112,14 +164,14 @@ namespace kagome::storage::trie { * 5.3 The Trie structure in the Polkadot Host specification */ - struct OpaqueTrieNode : public Node {}; + struct OpaqueTrieNode { + virtual ~OpaqueTrieNode() = default; + }; struct TrieNode : public OpaqueTrieNode { TrieNode() = default; TrieNode(KeyNibbles key_nibbles, ValueAndHash value) - : key_nibbles{std::move(key_nibbles)}, value{std::move(value)} {} - - ~TrieNode() override = default; + : key_nibbles_{std::move(key_nibbles)}, value_{std::move(value)} {} enum class Type { Special, // - @@ -132,10 +184,39 @@ namespace kagome::storage::trie { ReservedForCompactEncoding // 0001 0000 }; - inline bool isBranch() const noexcept; + virtual bool isBranch() const noexcept = 0; + + const KeyNibbles &getKeyNibbles() const { + return key_nibbles_; + } + + KeyNibbles &getMutKeyNibbles() { + return key_nibbles_; + } + + void setKeyNibbles(KeyNibbles &&key_nibbles) { + key_nibbles_ = std::move(key_nibbles); + } + + const ValueAndHash &getValue() const { + return value_; + } + + ValueAndHash &getMutableValue() { + return value_; + } - KeyNibbles key_nibbles; - ValueAndHash value; + void setValue(const ValueAndHash &new_value) { + value_ = new_value; + } + + void setValue(ValueAndHash &&new_value) { + value_ = std::move(new_value); + } + + private: + KeyNibbles key_nibbles_; + ValueAndHash value_; }; struct BranchNode : public TrieNode { @@ -144,10 +225,24 @@ namespace kagome::storage::trie { BranchNode() = default; explicit BranchNode(KeyNibbles key_nibbles, std::optional value = std::nullopt) - : TrieNode{std::move(key_nibbles), {std::nullopt, std::move(value)}} {} + : TrieNode{std::move(key_nibbles), {std::move(value), std::nullopt}} {} ~BranchNode() override = default; + uint8_t getNextChildIdxFrom(uint8_t child_idx) const { + while (child_idx < kMaxChildren) { + if (children[child_idx]) { + return child_idx; + } + child_idx++; + } + return kMaxChildren; + } + + virtual bool isBranch() const noexcept override { + return true; + } + uint16_t childrenBitmap() const; uint8_t childrenNum() const; @@ -157,17 +252,17 @@ namespace kagome::storage::trie { std::array, kMaxChildren> children; }; - bool TrieNode::isBranch() const noexcept { - return dynamic_cast(this) != nullptr; - } - struct LeafNode : public TrieNode { LeafNode() = default; - LeafNode(KeyNibbles key_nibbles, std::optional value) - : TrieNode{std::move(key_nibbles), {std::nullopt, std::move(value)}} {} + LeafNode(KeyNibbles key_nibbles, common::Buffer value) + : TrieNode{std::move(key_nibbles), {std::move(value), std::nullopt}} {} LeafNode(KeyNibbles key_nibbles, ValueAndHash value) : TrieNode{std::move(key_nibbles), std::move(value)} {} + virtual bool isBranch() const noexcept override { + return false; + } + ~LeafNode() override = default; }; @@ -181,22 +276,11 @@ namespace kagome::storage::trie { * @param key a storage key, which is a hash of an encoded node according to * PolkaDot specification */ - explicit DummyNode(common::Buffer key) : db_key{std::move(key)} {} + explicit DummyNode(const MerkleValue &key) : db_key{key} {} - common::Buffer db_key; + MerkleValue db_key; }; - // TODO(turuslan): #1470, refactor retrieve - /** - * Workaround to retrieve value from hash if value is not present. - * @see PolkadotTrieImpl::retrieveValue - * @see TrieSerializerImpl::retrieveNode - */ - struct DummyValue : OpaqueTrieNode { - DummyValue(ValueAndHash &value) : value{value} {} - - ValueAndHash &value; - }; } // namespace kagome::storage::trie template <> diff --git a/core/storage/trie/serialization/codec.hpp b/core/storage/trie/serialization/codec.hpp new file mode 100644 index 0000000000..6e4fc650ea --- /dev/null +++ b/core/storage/trie/serialization/codec.hpp @@ -0,0 +1,87 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_TRIE_CODEC_HPP +#define KAGOME_TRIE_CODEC_HPP + +#include "common/blob.hpp" +#include "common/buffer.hpp" +#include "storage/trie/polkadot_trie/trie_node.hpp" +#include "storage/trie/types.hpp" + +namespace kagome::storage::trie { + struct TrieNode; + + /** + * @brief Internal codec for nodes in the Trie. + */ + class Codec { + public: + struct ChildData { + const TrieNode &child; + const MerkleValue &merkle_value; + common::Buffer encoding; + }; + struct ValueData { + const TrieNode &node; + common::Hash256 hash; + const common::Buffer &value; + }; + using Visitee = std::variant; + using ChildVisitor = std::function(Visitee)>; + + static constexpr auto NoopChildVisitor = + [](Visitee) -> outcome::result { + return outcome::success(); + }; + + virtual ~Codec() = default; + + /** + * @brief Encode node to byte representation and store children + * @param node node in the trie + * @param version the version of trie encoding algorithm + * @param child_visitor function invoked for every child in a branch node + * recursively + * @return encoded representation of a {@param node} + */ + virtual outcome::result encodeNode( + const TrieNode &node, + StateVersion version, + const ChildVisitor &child_visitor = NoopChildVisitor) const = 0; + + /** + * @brief Decode node from bytes + * @param encoded_data a buffer containing encoded representation of a node + * @return a node in the trie + */ + virtual outcome::result> decodeNode( + common::BufferView encoded_data) const = 0; + + /** + * @brief Get the merkle value of a node + * @param buf byte representation of the node + * @return hash of \param buf or \param buf if it is shorter than the hash + */ + virtual MerkleValue merkleValue(const common::BufferView &buf) const = 0; + virtual outcome::result merkleValue( + const OpaqueTrieNode &node, + StateVersion version, + const ChildVisitor &child_visitor = NoopChildVisitor) const = 0; + + /** + * @brief Get the hash of a node + * @param buf byte representation of the node + * @return hash of \param buf + */ + virtual common::Hash256 hash256(const common::BufferView &buf) const = 0; + + virtual bool shouldBeHashed(const ValueAndHash &value, + StateVersion version) const = 0; + }; + +} // namespace kagome::storage::trie + +#endif // KAGOME_TRIE_CODEC_HPP diff --git a/core/storage/trie/serialization/ordered_trie_hash.hpp b/core/storage/trie/serialization/ordered_trie_hash.hpp index 2688bcd1be..21c3ba6f7a 100644 --- a/core/storage/trie/serialization/ordered_trie_hash.hpp +++ b/core/storage/trie/serialization/ordered_trie_hash.hpp @@ -23,10 +23,10 @@ namespace kagome::storage::trie { * @return the Merkle tree root hash of the tree containing provided values */ template - outcome::result calculateOrderedTrieHash(StateVersion version, - const It &begin, - const It &end) { - PolkadotTrieImpl trie; + outcome::result calculateOrderedTrieHash(StateVersion version, + const It &begin, + const It &end) { + auto trie = storage::trie::PolkadotTrieImpl::createEmpty(); PolkadotCodec codec; // empty root if (begin == end) { @@ -38,15 +38,15 @@ namespace kagome::storage::trie { scale::CompactInteger key = 0; while (it != end) { OUTCOME_TRY(enc, scale::encode(key++)); - OUTCOME_TRY(trie.put(enc, BufferView{*it})); + OUTCOME_TRY(trie->put(enc, BufferView{*it})); it++; } - OUTCOME_TRY(enc, codec.encodeNode(*trie.getRoot(), version, {})); - return common::Buffer{codec.hash256(enc)}; + OUTCOME_TRY(enc, codec.encodeNode(*trie->getRoot(), version, {})); + return codec.hash256(enc); } template - inline outcome::result calculateOrderedTrieHash( + inline outcome::result calculateOrderedTrieHash( StateVersion version, const ContainerType &container) { return calculateOrderedTrieHash( version, container.begin(), container.end()); diff --git a/core/storage/trie/serialization/polkadot_codec.cpp b/core/storage/trie/serialization/polkadot_codec.cpp index 2ce2e02d9a..85fcd8c029 100644 --- a/core/storage/trie/serialization/polkadot_codec.cpp +++ b/core/storage/trie/serialization/polkadot_codec.cpp @@ -6,6 +6,7 @@ #include "storage/trie/serialization/polkadot_codec.hpp" #include "crypto/blake2/blake2b.h" +#include "log/logger.hpp" #include "scale/scale.hpp" #include "scale/scale_decoder_stream.hpp" #include "storage/trie/polkadot_trie/trie_node.hpp" @@ -38,8 +39,9 @@ namespace kagome::storage::trie { return out; } - inline bool shouldBeHashed(const TrieNode &node, StateVersion version) { - if (node.value.hash || !node.value.value) { + bool PolkadotCodec::shouldBeHashed(const ValueAndHash &value, + StateVersion version) const { + if (value.hash || !value.value) { return false; } switch (version) { @@ -47,10 +49,10 @@ namespace kagome::storage::trie { return false; } case StateVersion::V1: { - if (!node.value.dirty()) { + if (!value.dirty()) { return false; } - return node.value.value->size() >= kMaxInlineValueSizeVersion1; + return value.value->size() >= kMaxInlineValueSizeVersion1; } } BOOST_UNREACHABLE_RETURN(); @@ -58,38 +60,43 @@ namespace kagome::storage::trie { inline TrieNode::Type getType(const TrieNode &node) { if (node.isBranch()) { - if (node.value.hash) { + if (node.getValue().hash) { return TrieNode::Type::BranchContainingHashes; } - if (node.value.value) { + if (node.getValue().value) { return TrieNode::Type::BranchWithValue; } return TrieNode::Type::BranchEmptyValue; } - if (node.value.hash) { + if (node.getValue().hash) { return TrieNode::Type::LeafContainingHashes; } - if (node.value.value) { + if (node.getValue().value) { return TrieNode::Type::Leaf; } return TrieNode::Type::Empty; } - common::Buffer PolkadotCodec::merkleValue( - const common::BufferView &buf) const { + MerkleValue PolkadotCodec::merkleValue(const common::BufferView &buf) const { // if a buffer size is less than the size of a would-be hash, just return // this buffer to save space if (static_cast(buf.size()) < common::Hash256::size()) { - return common::Buffer{buf}; + return *MerkleValue::create(buf); } - return Buffer{hash256(buf)}; + return MerkleValue{hash256(buf)}; } - bool PolkadotCodec::isMerkleHash(const common::BufferView &buf) const { - const auto size = static_cast(buf.size()); - BOOST_ASSERT(size <= common::Hash256::size()); - return size == common::Hash256::size(); + outcome::result PolkadotCodec::merkleValue( + const OpaqueTrieNode &node, + StateVersion version, + const ChildVisitor &child_visitor) const { + if (auto dummy = dynamic_cast(&node); dummy != nullptr) { + return dummy->db_key; + } + auto &trie_node = static_cast(node); + OUTCOME_TRY(enc, encodeNode(trie_node, version, child_visitor)); + return merkleValue(enc); } common::Hash256 PolkadotCodec::hash256(const common::BufferView &buf) const { @@ -106,21 +113,25 @@ namespace kagome::storage::trie { } outcome::result PolkadotCodec::encodeNode( - const Node &node, + const TrieNode &node, StateVersion version, - const StoreChildren &store_children) const { - auto &trie = dynamic_cast(node); - if (trie.isBranch()) { + const ChildVisitor &child_visitor) const { + auto *trie_node = dynamic_cast(&node); + if (trie_node == nullptr) { + auto &dummy_node = dynamic_cast(node); + return dummy_node.db_key.asBuffer(); + } + if (trie_node->isBranch()) { return encodeBranch( - dynamic_cast(node), version, store_children); + dynamic_cast(node), version, child_visitor); } return encodeLeaf( - dynamic_cast(node), version, store_children); + dynamic_cast(node), version, child_visitor); } outcome::result PolkadotCodec::encodeHeader( const TrieNode &node, StateVersion version) const { - if (node.key_nibbles.size() > 0xffffu) { + if (node.getKeyNibbles().size() > 0xffffu) { return Error::TOO_MANY_NIBBLES; } @@ -128,7 +139,7 @@ namespace kagome::storage::trie { uint8_t partial_length_mask; // max partial key length auto type = getType(node); - if (shouldBeHashed(node, version)) { + if (shouldBeHashed(node.getValue(), version)) { if (node.isBranch()) { type = TrieNode::Type::BranchContainingHashes; } else { @@ -171,15 +182,15 @@ namespace kagome::storage::trie { } // set bits of partial key length - if (node.key_nibbles.size() < partial_length_mask) { - head |= uint8_t(node.key_nibbles.size()); + if (node.getKeyNibbles().size() < partial_length_mask) { + head |= uint8_t(node.getKeyNibbles().size()); return Buffer{head}; // header contains 1 byte } // if partial key length is greater than max partial key length, then the // rest of the length is stored in consequent bytes head += partial_length_mask; - size_t l = node.key_nibbles.size() - partial_length_mask; + size_t l = node.getKeyNibbles().size() - partial_length_mask; Buffer out(1u + // 1 byte head l / 0xffu + // number of 255 in l 1u, // for last byte @@ -198,19 +209,20 @@ namespace kagome::storage::trie { common::Buffer &out, const TrieNode &node, StateVersion version, - const StoreChildren &store_children) const { - auto hash = node.value.hash; - if (shouldBeHashed(node, version)) { - hash = hash256(*node.value.value); - if (store_children) { - OUTCOME_TRY(store_children(nullptr, *hash, Buffer{*node.value.value})); + const ChildVisitor &child_visitor) const { + auto hash = node.getValue().hash; + if (shouldBeHashed(node.getValue(), version)) { + hash = hash256(*node.getValue().value); + if (child_visitor) { + OUTCOME_TRY( + child_visitor(ValueData{node, *hash, *node.getValue().value})); } } if (hash) { out += *hash; - } else if (node.value.value) { + } else if (node.getValue().value) { // TODO(turuslan): soramitsu/scale-codec-cpp non-allocating methods - OUTCOME_TRY(value, scale::encode(*node.value.value)); + OUTCOME_TRY(value, scale::encode(*node.getValue().value)); out += value; } return outcome::success(); @@ -219,17 +231,17 @@ namespace kagome::storage::trie { outcome::result PolkadotCodec::encodeBranch( const BranchNode &node, StateVersion version, - const StoreChildren &store_children) const { + const ChildVisitor &child_visitor) const { // node header OUTCOME_TRY(encoding, encodeHeader(node, version)); // key - encoding += node.key_nibbles.toByteBuffer(); + encoding += node.getKeyNibbles().toByteBuffer(); // children bitmap encoding += ushortToBytes(node.childrenBitmap()); - OUTCOME_TRY(encodeValue(encoding, node, version, store_children)); + OUTCOME_TRY(encodeValue(encoding, node, version, child_visitor)); // encode each child for (auto &child : node.children) { @@ -237,16 +249,18 @@ namespace kagome::storage::trie { if (auto dummy = std::dynamic_pointer_cast(child); dummy != nullptr) { auto merkle_value = dummy->db_key; - OUTCOME_TRY(scale_enc, scale::encode(std::move(merkle_value))); + OUTCOME_TRY(scale_enc, scale::encode(merkle_value.asBuffer())); encoding.put(scale_enc); } else { - OUTCOME_TRY(enc, encodeNode(*child, version, store_children)); + // because a node is either a dummy or a trienode + auto& child_node = dynamic_cast(*child); + OUTCOME_TRY(enc, encodeNode(child_node, version, child_visitor)); auto merkle = merkleValue(enc); - if (isMerkleHash(merkle) && store_children) { - auto ptr = dynamic_cast(child.get()); - OUTCOME_TRY(store_children(ptr, merkle, std::move(enc))); + if (merkle.isHash() && child_visitor) { + OUTCOME_TRY( + child_visitor(ChildData{child_node, merkle, std::move(enc)})); } - OUTCOME_TRY(scale_enc, scale::encode(merkle)); + OUTCOME_TRY(scale_enc, scale::encode(merkle.asBuffer())); encoding.put(scale_enc); } } @@ -258,23 +272,23 @@ namespace kagome::storage::trie { outcome::result PolkadotCodec::encodeLeaf( const LeafNode &node, StateVersion version, - const StoreChildren &store_children) const { + const ChildVisitor &child_visitor) const { OUTCOME_TRY(encoding, encodeHeader(node, version)); // key - encoding += node.key_nibbles.toByteBuffer(); + encoding += node.getKeyNibbles().toByteBuffer(); - if (!node.value) { + if (!node.getValue()) { return Error::NO_NODE_VALUE; } - OUTCOME_TRY(encodeValue(encoding, node, version, store_children)); + OUTCOME_TRY(encodeValue(encoding, node, version, child_visitor)); return outcome::success(std::move(encoding)); } - outcome::result> PolkadotCodec::decodeNode( - gsl::span encoded_data) const { + outcome::result> PolkadotCodec::decodeNode( + common::BufferView encoded_data) const { BufferStream stream{encoded_data}; // decode the header with the node type and the partial key length OUTCOME_TRY(header, decodeHeader(stream)); @@ -287,7 +301,7 @@ namespace kagome::storage::trie { case TrieNode::Type::Leaf: { OUTCOME_TRY(value, scale::decode(stream.leftBytes())); return std::make_shared( - partial_key, ValueAndHash{std::nullopt, std::move(value), false}); + partial_key, ValueAndHash{std::move(value), std::nullopt, false}); } case TrieNode::Type::BranchEmptyValue: @@ -297,7 +311,7 @@ namespace kagome::storage::trie { case TrieNode::Type::LeafContainingHashes: { OUTCOME_TRY(hash, scale::decode(stream.leftBytes())); return std::make_shared( - partial_key, ValueAndHash{hash, std::nullopt, false}); + partial_key, ValueAndHash{std::nullopt, hash, false}); } case TrieNode::Type::BranchContainingHashes: @@ -355,7 +369,7 @@ namespace kagome::storage::trie { size_t pk_length = first & partial_key_length_mask; if (pk_length == partial_key_length_mask) { - uint8_t read_length = 0; + uint8_t read_length{}; do { if (not stream.hasMore(1)) { return Error::INPUT_TOO_SMALL; @@ -388,7 +402,7 @@ namespace kagome::storage::trie { return partial_key_nibbles; } - outcome::result> PolkadotCodec::decodeBranch( + outcome::result> PolkadotCodec::decodeBranch( TrieNode::Type type, const KeyNibbles &partial_key, BufferStream &stream) const { @@ -412,7 +426,7 @@ namespace kagome::storage::trie { } catch (std::system_error &e) { return outcome::failure(e.code()); } - node->value = {std::nullopt, std::move(value), false}; + node->setValue({std::move(value), std::nullopt, false}); } else if (type == TrieNode::Type::BranchContainingHashes) { common::Hash256 hash; try { @@ -420,7 +434,7 @@ namespace kagome::storage::trie { } catch (std::system_error &e) { return outcome::failure(e.code()); } - node->value = {hash, std::nullopt, false}; + node->setValue({std::nullopt, hash, false}); } else if (type != TrieNode::Type::BranchEmptyValue) { return Error::UNKNOWN_NODE_TYPE; } @@ -439,7 +453,9 @@ namespace kagome::storage::trie { } catch (std::system_error &e) { return outcome::failure(e.code()); } - node->children.at(i) = std::make_shared(child_hash); + // SAFETY: database cannot contain invalid merkle values + node->children.at(i) = std::make_shared( + MerkleValue::create(child_hash).value()); } i++; } diff --git a/core/storage/trie/serialization/polkadot_codec.hpp b/core/storage/trie/serialization/polkadot_codec.hpp index f17fd8d8be..612115775f 100644 --- a/core/storage/trie/serialization/polkadot_codec.hpp +++ b/core/storage/trie/serialization/polkadot_codec.hpp @@ -10,7 +10,7 @@ #include #include -#include "storage/trie/codec.hpp" +#include "codec.hpp" #include "storage/trie/polkadot_trie/trie_node.hpp" #include "storage/trie/serialization/buffer_stream.hpp" @@ -32,16 +32,18 @@ namespace kagome::storage::trie { ~PolkadotCodec() override = default; outcome::result encodeNode( - const Node &node, + const TrieNode &node, StateVersion version, - const StoreChildren &store_children) const override; - - outcome::result> decodeNode( - gsl::span encoded_data) const override; + const ChildVisitor &child_visitor = NoopChildVisitor) const override; - common::Buffer merkleValue(const BufferView &buf) const override; + outcome::result> decodeNode( + BufferView encoded_data) const override; - bool isMerkleHash(const common::BufferView &buf) const override; + MerkleValue merkleValue(const BufferView &buf) const override; + outcome::result merkleValue( + const OpaqueTrieNode &node, + StateVersion version, + const ChildVisitor &child_visitor = NoopChildVisitor) const override; common::Hash256 hash256(const BufferView &buf) const override; @@ -57,15 +59,15 @@ namespace kagome::storage::trie { common::Buffer &out, const TrieNode &node, StateVersion version, - const StoreChildren &store_children) const; + const ChildVisitor &child_visitor = NoopChildVisitor) const; + outcome::result encodeBranch( const BranchNode &node, StateVersion version, - const StoreChildren &store_children) const; - outcome::result encodeLeaf( - const LeafNode &node, - StateVersion version, - const StoreChildren &store_children) const; + const ChildVisitor &child_visitor) const; + outcome::result encodeLeaf(const LeafNode &node, + StateVersion version, + const ChildVisitor &child_visitor) const; outcome::result> decodeHeader( BufferStream &stream) const; @@ -73,10 +75,13 @@ namespace kagome::storage::trie { outcome::result decodePartialKey(size_t nibbles_num, BufferStream &stream) const; - outcome::result> decodeBranch( + outcome::result> decodeBranch( TrieNode::Type type, const KeyNibbles &partial_key, BufferStream &stream) const; + + bool shouldBeHashed(const ValueAndHash &value, + StateVersion version) const override; }; } // namespace kagome::storage::trie diff --git a/core/storage/trie/serialization/trie_serializer.hpp b/core/storage/trie/serialization/trie_serializer.hpp index 090682ee32..d5df855836 100644 --- a/core/storage/trie/serialization/trie_serializer.hpp +++ b/core/storage/trie/serialization/trie_serializer.hpp @@ -39,8 +39,29 @@ namespace kagome::storage::trie { * is no entry for provided key. */ virtual outcome::result> retrieveTrie( - const common::Buffer &db_key, - OnNodeLoaded on_node_loaded) const = 0; + RootHash db_key, + OnNodeLoaded on_node_loaded = [](EncodedNode) {}) const = 0; + + /** + * Fetches a node from the storage. A nullptr is returned in case that there + * is no entry for provided key. Mind that a branch node will have dummy + * nodes as its children + */ + virtual outcome::result retrieveNode( + MerkleValue db_key, + const OnNodeLoaded &on_node_loaded = [](EncodedNode) {}) const = 0; + + /** + * Retrieves a node, replacing a dummy node to an actual node if + * needed + */ + virtual outcome::result retrieveNode( + const std::shared_ptr &node, + const OnNodeLoaded &on_node_loaded = [](EncodedNode) {}) const = 0; + + virtual outcome::result> retrieveValue( + const common::Hash256 &hash, + const OnNodeLoaded &on_node_loaded) const = 0; }; } // namespace kagome::storage::trie diff --git a/core/storage/trie/serialization/trie_serializer_impl.cpp b/core/storage/trie/serialization/trie_serializer_impl.cpp index 97cad017a6..d27155a644 100644 --- a/core/storage/trie/serialization/trie_serializer_impl.cpp +++ b/core/storage/trie/serialization/trie_serializer_impl.cpp @@ -5,10 +5,11 @@ #include "storage/trie/serialization/trie_serializer_impl.hpp" +#include "common/monadic_utils.hpp" #include "outcome/outcome.hpp" -#include "storage/trie/codec.hpp" #include "storage/trie/polkadot_trie/polkadot_trie_factory.hpp" #include "storage/trie/polkadot_trie/trie_node.hpp" +#include "storage/trie/serialization/codec.hpp" #include "storage/trie/trie_storage_backend.hpp" namespace kagome::storage::trie { @@ -37,77 +38,106 @@ namespace kagome::storage::trie { } outcome::result> - TrieSerializerImpl::retrieveTrie(const common::Buffer &db_key, + TrieSerializerImpl::retrieveTrie(RootHash db_key, OnNodeLoaded on_node_loaded) const { - PolkadotTrie::NodeRetrieveFunctor f = + PolkadotTrie::NodeRetrieveFunction f = [this, on_node_loaded](const std::shared_ptr &parent) -> outcome::result { OUTCOME_TRY(node, retrieveNode(parent, on_node_loaded)); return std::move(node); }; + PolkadotTrie::ValueRetrieveFunction v = + [this, on_node_loaded](const common::Hash256 &hash) + -> outcome::result> { + OUTCOME_TRY(value, retrieveValue(hash, on_node_loaded)); + return value; + }; if (db_key == getEmptyRootHash()) { - return trie_factory_->createEmpty(std::move(f)); + return trie_factory_->createEmpty( + PolkadotTrie::RetrieveFunctions{std::move(f), std::move(v)}); } OUTCOME_TRY(root, retrieveNode(db_key, on_node_loaded)); - return trie_factory_->createFromRoot(std::move(root), std::move(f)); + return trie_factory_->createFromRoot( + std::move(root), + PolkadotTrie::RetrieveFunctions{std::move(f), std::move(v)}); } outcome::result TrieSerializerImpl::storeRootNode( TrieNode &node, StateVersion version) { auto batch = backend_->batch(); + BOOST_ASSERT(batch != nullptr); - OUTCOME_TRY(enc, - codec_->encodeNode(node, - version, - [&](const TrieNode *, - common::BufferView hash, - common::Buffer &&encoded) { - return batch->put(hash, - std::move(encoded)); - })); - auto key = codec_->hash256(enc); - OUTCOME_TRY(batch->put(key, std::move(enc))); + OUTCOME_TRY( + enc, + codec_->encodeNode( + node, + version, + [&](Codec::Visitee visitee) -> outcome::result { + if (auto child_data = std::get_if(&visitee); + child_data != nullptr) { + if (child_data->merkle_value.isHash()) { + return batch->put(child_data->merkle_value.asBuffer(), + std::move(child_data->encoding)); + } else { + return outcome::success(); // nodes which encoding is shorter + // than its hash are not stored in + // the DB separately + } + } + auto value_data = std::get(visitee); + // value_data.value is a reference to a buffer stored outside of + // this lambda, so taking its view should be okay + return batch->put(value_data.hash, value_data.value.view()); + })); + auto hash = codec_->hash256(enc); + OUTCOME_TRY(batch->put(hash, std::move(enc))); OUTCOME_TRY(batch->commit()); - return key; + return hash; } outcome::result TrieSerializerImpl::retrieveNode( - const std::shared_ptr &parent, + const std::shared_ptr &node, const OnNodeLoaded &on_node_loaded) const { - if (auto p = std::dynamic_pointer_cast(parent); p != nullptr) { - OUTCOME_TRY(value, backend_->get(*p->value.hash)); - if (on_node_loaded) { - on_node_loaded(value); - } - p->value.value = value.into(); - return nullptr; - } - if (auto p = std::dynamic_pointer_cast(parent); p != nullptr) { + if (auto p = std::dynamic_pointer_cast(node); p != nullptr) { OUTCOME_TRY(n, retrieveNode(p->db_key, on_node_loaded)); return std::move(n); } - return std::dynamic_pointer_cast(parent); + return std::dynamic_pointer_cast(node); } outcome::result TrieSerializerImpl::retrieveNode( - const common::Buffer &db_key, const OnNodeLoaded &on_node_loaded) const { - if (db_key.empty() or db_key == getEmptyRootHash()) { + MerkleValue db_key, const OnNodeLoaded &on_node_loaded) const { + if (db_key.asHash() == getEmptyRootHash()) { return nullptr; } Buffer enc; - if (codec_->isMerkleHash(db_key)) { - OUTCOME_TRY(db, backend_->get(db_key)); + if (db_key.isHash()) { + OUTCOME_TRY(db, backend_->get(db_key.asBuffer())); if (on_node_loaded) { on_node_loaded(db); } - enc = db.into(); + enc = db.intoBuffer(); } else { // `isMerkleHash(db_key) == false` means `db_key` is value itself - enc = db_key; + enc = db_key.asBuffer(); } OUTCOME_TRY(n, codec_->decodeNode(enc)); - return std::dynamic_pointer_cast(n); + auto node = std::dynamic_pointer_cast(n); + return node; + } + + outcome::result> + TrieSerializerImpl::retrieveValue(const common::Hash256 &hash, + const OnNodeLoaded &on_node_loaded) const { + OUTCOME_TRY(value, backend_->tryGet(hash)); + return common::map_optional(std::move(value), + [&](common::BufferOrView &&value) { + if (on_node_loaded) { + on_node_loaded(value); + } + return value.intoBuffer(); + }); } } // namespace kagome::storage::trie diff --git a/core/storage/trie/serialization/trie_serializer_impl.hpp b/core/storage/trie/serialization/trie_serializer_impl.hpp index c6e41e575a..6152e46d00 100644 --- a/core/storage/trie/serialization/trie_serializer_impl.hpp +++ b/core/storage/trie/serialization/trie_serializer_impl.hpp @@ -33,24 +33,15 @@ namespace kagome::storage::trie { StateVersion version) override; outcome::result> retrieveTrie( - const common::Buffer &db_key, - OnNodeLoaded on_node_loaded) const override; + RootHash db_key, OnNodeLoaded on_node_loaded) const override; - private: - /** - * Writes a node to a persistent storage, recursively storing its - * descendants as well. Then replaces the node children to dummy nodes to - * avoid memory waste - */ - outcome::result storeRootNode(TrieNode &node, - StateVersion version); /** * Fetches a node from the storage. A nullptr is returned in case that there * is no entry for provided key. Mind that a branch node will have dummy * nodes as its children */ outcome::result retrieveNode( - const common::Buffer &db_key, const OnNodeLoaded &on_node_loaded) const; + MerkleValue db_key, const OnNodeLoaded &on_node_loaded) const override; /** * Retrieves a node, replacing a dummy node to an actual node if @@ -58,7 +49,20 @@ namespace kagome::storage::trie { */ outcome::result retrieveNode( const std::shared_ptr &node, - const OnNodeLoaded &on_node_loaded) const; + const OnNodeLoaded &on_node_loaded) const override; + + outcome::result> retrieveValue( + const common::Hash256 &hash, + const OnNodeLoaded &on_node_loaded) const override; + + private: + /** + * Writes a node to a persistent storage, recursively storing its + * descendants as well. Then replaces the node children to dummy nodes to + * avoid memory waste + */ + outcome::result storeRootNode(TrieNode &node, + StateVersion version); std::shared_ptr trie_factory_; std::shared_ptr codec_; diff --git a/core/storage/trie_pruner/design.md b/core/storage/trie_pruner/design.md new file mode 100644 index 0000000000..d113e1c047 --- /dev/null +++ b/core/storage/trie_pruner/design.md @@ -0,0 +1,54 @@ +# Trie Pruner + +## Goal + +While the Kagome node is running, remove obsolete trie nodes from the database to reduce its size. + +## Possible implementations + +### Sliding window + +Maintain the list of nodes deleted in the past N blocks. +When a node is deleted for longer than N blocks, prune it. + +Insertion: remove new nodes from the list of deleted nodes using index. + +Deletion: add deleted nodes to the list. Requires full traversal of deleted sub-trees. + +Clear prefix: add all deleted nodes to the list. + +Pruning: When a new block is finalized, remove all nodes deleted in the block #N+1. + +Issues: nodes are re-used in the current implementation. + +### Reference counting + +Each node contains a reference counter. + +Insertion: - + +Deletion: - + +Clear prefix: - + +Commit: all nodes reachable from new root increase their reference count. + +Pruning: take state of block Current - N - 1, decrease reference count of all nodes reachable from this state by 1, remove all reachable nodes with reference count that becomed 0. + +### Whitelist + +Collect nodes reachable from most recent state roots, delete all other nodes. + +Insertion: - + +Deletion: - + +Clear prefix: - + +Pruning: collected a set S of all nodes reachable from the given set of root nodes, delete all nodes not in S. + +Issues: performance. + +Assume base is pruned, meaning it contains RootNum states of NodeNum nodes in total. The current block is BlockN. +Let's build set S of all stored nodes. +When building state of BlockN+1, create a set S_new of all created nodes which are not in S and removed nodes S_del which are in S. diff --git a/core/storage/trie_pruner/impl/recover_pruner_state.cpp b/core/storage/trie_pruner/impl/recover_pruner_state.cpp new file mode 100644 index 0000000000..d754f3e26f --- /dev/null +++ b/core/storage/trie_pruner/impl/recover_pruner_state.cpp @@ -0,0 +1,62 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "storage/trie_pruner/recover_pruner_state.hpp" + +#include "blockchain/block_tree.hpp" +#include "log/logger.hpp" +#include "storage/trie_pruner/trie_pruner.hpp" + +namespace kagome::storage::trie_pruner { + + outcome::result recoverPrunerState( + TriePruner &pruner, const blockchain::BlockTree &block_tree) { + static log::Logger logger = + log::createLogger("PrunerStateRecovery", "storage"); + auto last_pruned_block = pruner.getLastPrunedBlock(); + if (!last_pruned_block.has_value()) { + OUTCOME_TRY(first_hash_opt, block_tree.getBlockHash(1)); + if (first_hash_opt.has_value()) { + SL_WARN(logger, + "Running pruner on a non-empty non-pruned storage may lead to " + "skipping some stored states."); + OUTCOME_TRY( + last_finalized, + block_tree.getBlockHeader(block_tree.getLastFinalized().hash)); + + if (auto res = pruner.restoreState(last_finalized, block_tree); + res.has_error()) { + SL_ERROR(logger, + "Failed to restore trie pruner state starting from last " + "finalized " + "block: {}", + res.error().message()); + return res.as_failure(); + } + } else { + OUTCOME_TRY( + genesis_header, + block_tree.getBlockHeader(block_tree.getGenesisBlockHash())); + OUTCOME_TRY(pruner.addNewState(genesis_header.state_root, + trie::StateVersion::V0)); + } + } else { + OUTCOME_TRY(base_block_header, + block_tree.getBlockHeader(last_pruned_block.value().hash)); + BOOST_ASSERT(block_tree.getLastFinalized().number + >= last_pruned_block.value().number); + if (auto res = pruner.restoreState(base_block_header, block_tree); + res.has_error()) { + SL_WARN(logger, + "Failed to restore trie pruner state starting from base " + "block {}: {}", + last_pruned_block.value(), + res.error().message()); + } + } + return outcome::success(); + } + +} // namespace kagome::storage::trie_pruner \ No newline at end of file diff --git a/core/storage/trie_pruner/impl/trie_pruner_impl.cpp b/core/storage/trie_pruner/impl/trie_pruner_impl.cpp new file mode 100644 index 0000000000..fbf0d1b174 --- /dev/null +++ b/core/storage/trie_pruner/impl/trie_pruner_impl.cpp @@ -0,0 +1,499 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "storage/trie_pruner/impl/trie_pruner_impl.hpp" + +#include + +#include + +#include "application/app_configuration.hpp" +#include "application/app_state_manager.hpp" +#include "blockchain/block_storage.hpp" +#include "blockchain/block_tree.hpp" +#include "crypto/hasher/hasher_impl.hpp" +#include "storage/database_error.hpp" +#include "storage/predefined_keys.hpp" +#include "storage/spaced_storage.hpp" +#include "storage/trie/polkadot_trie/polkadot_trie.hpp" +#include "storage/trie/serialization/polkadot_codec.hpp" +#include "storage/trie/serialization/trie_serializer.hpp" +#include "storage/trie/trie_storage_backend.hpp" + +OUTCOME_CPP_DEFINE_CATEGORY(kagome::storage::trie_pruner, + TriePrunerImpl::Error, + e) { + using E = kagome::storage::trie_pruner::TriePrunerImpl::Error; + switch (e) { + case E::LAST_PRUNED_BLOCK_IS_LAST_FINALIZED: + return "Last pruned block is the last finalized block, so the trie " + "pruner cannot register the next block state"; + } + return "Unknown TriePruner error"; +} + +namespace kagome::storage::trie_pruner { + + template , + F, + common::BufferView, + const trie::RootHash &>, + bool> = true> + outcome::result forEachChildTrie(const trie::PolkadotTrie &parent, + const F &f) { + auto child_tries = parent.trieCursor(); + OUTCOME_TRY(child_tries->seekLowerBound(storage::kChildStoragePrefix)); + while (child_tries->isValid() + && child_tries->key().value().startsWith( + storage::kChildStoragePrefix)) { + auto child_key = child_tries->value().value(); + OUTCOME_TRY(child_hash, trie::RootHash::fromSpan(child_key)); + OUTCOME_TRY(f(child_key.view(), child_hash)); + OUTCOME_TRY(child_tries->next()); + } + return outcome::success(); + } + + TriePrunerImpl::TriePrunerImpl( + std::shared_ptr app_state_manager, + std::shared_ptr trie_storage, + std::shared_ptr serializer, + std::shared_ptr codec, + std::shared_ptr storage, + std::shared_ptr hasher, + std::shared_ptr config) + : trie_storage_{trie_storage}, + serializer_{serializer}, + codec_{codec}, + storage_{storage}, + hasher_{hasher}, + pruning_depth_{config->statePruningDepth()} { + BOOST_ASSERT(trie_storage_ != nullptr); + BOOST_ASSERT(serializer_ != nullptr); + BOOST_ASSERT(codec_ != nullptr); + BOOST_ASSERT(storage_ != nullptr); + BOOST_ASSERT(hasher_ != nullptr); + + app_state_manager->takeControl(*this); + } + + bool TriePrunerImpl::prepare() { + BOOST_ASSERT(storage_->getSpace(kDefault)); + auto encoded_info_res = + storage_->getSpace(kDefault)->tryGet(TRIE_PRUNER_INFO_KEY); + if (!encoded_info_res) { + SL_ERROR(logger_, "Failed to obtain trie pruner metadata"); + return false; + } + auto &encoded_info = encoded_info_res.value(); + + if (encoded_info.has_value()) { + if (auto info_res = scale::decode(*encoded_info); + info_res.has_value()) { + auto &info = info_res.value(); + last_pruned_block_ = info.last_pruned_block; + } else { + SL_ERROR(logger_, "Failed to decode pruner info: {}", info_res.error()); + return false; + } + } + SL_DEBUG( + logger_, + "Initialize trie pruner with pruning depth {}, last pruned block {}", + pruning_depth_, + last_pruned_block_); + return true; + } + + class EncoderCache { + public: + explicit EncoderCache(const trie::Codec &codec, log::Logger logger) + : codec{codec}, logger{logger} {} + + ~EncoderCache() { + SL_DEBUG(logger, "Encode called {} times", encode_called); + } + + trie::MerkleValue getMerkleValue(const trie::DummyNode &node) const { + return node.db_key; + } + + std::optional getValueHash(const trie::TrieNode &node, + trie::StateVersion version) { + if (node.getValue().is_some()) { + if (node.getValue().hash.has_value()) { + return *node.getValue().hash; + } + if (codec.shouldBeHashed(node.getValue(), version)) { + return codec.hash256(*node.getValue().value); + } + } + return std::nullopt; + } + + outcome::result getMerkleValue( + const trie::TrieNode &node, trie::StateVersion version) { + encode_called++; + // TODO(Harrm): cache is broken and thus temporarily disabled + OUTCOME_TRY( + merkle_value, + codec.merkleValue( + node, + version, + [this](trie::Codec::Visitee visitee) -> outcome::result { + if (auto child_data = + std::get_if(&visitee); + child_data != nullptr) { + return visitChild(child_data->child, + child_data->merkle_value); + } + return outcome::success(); + })); + return merkle_value; + } + + private: + const trie::Codec &codec; + size_t encode_called = 0; + log::Logger logger; + + outcome::result visitChild(const trie::TrieNode &node, + const trie::MerkleValue &merkle_value) { + if (merkle_value.isHash()) { + encode_called++; + } + return outcome::success(); + } + }; + + outcome::result TriePrunerImpl::pruneFinalized( + const primitives::BlockHeader &block) { + auto batch = trie_storage_->batch(); + OUTCOME_TRY(prune(*batch, block.state_root)); + OUTCOME_TRY(batch->commit()); + + OUTCOME_TRY(block_enc, scale::encode(block)); + auto block_hash = hasher_->blake2b_256(block_enc); + + last_pruned_block_ = primitives::BlockInfo{block_hash, block.number}; + OUTCOME_TRY(savePersistentState()); + return outcome::success(); + } + + outcome::result TriePrunerImpl::pruneDiscarded( + const primitives::BlockHeader &block) { + // should prune even when pruning depth is none + auto batch = trie_storage_->batch(); + OUTCOME_TRY(prune(*batch, block.state_root)); + OUTCOME_TRY(batch->commit()); + return outcome::success(); + } + + outcome::result TriePrunerImpl::prune(BufferBatch &batch, + const trie::RootHash &root_hash) { + auto trie_res = serializer_->retrieveTrie(root_hash, nullptr); + if (trie_res.has_error() + && trie_res.error() == storage::DatabaseError::NOT_FOUND) { + SL_TRACE(logger_, + "Failed to obtain trie from storage, the state {} is probably " + "already pruned or has never been executed.", + root_hash); + return outcome::success(); + } + KAGOME_PROFILE_START_L(logger_, prune_state); + + OUTCOME_TRY(trie, trie_res); + if (trie->getRoot() == nullptr) { + SL_DEBUG(logger_, "Attempt to prune a trie with a null root"); + return outcome::success(); + } + + OUTCOME_TRY( + forEachChildTrie(*trie, + [this, &batch](common::BufferView child_key, + const trie::RootHash &child_hash) { + return prune(batch, child_hash); + })); + + size_t nodes_removed = 0; + size_t values_removed = 0; + size_t nodes_unknown = 0; + size_t values_unknown = 0; + + struct Entry { + common::Hash256 hash; + std::shared_ptr node; + size_t depth; + }; + std::vector queued_nodes; + queued_nodes.push_back({root_hash, trie->getRoot(), 0}); + + EncoderCache encoder{*codec_, logger_}; + + logger_->debug("Prune state root {}", root_hash); + + std::scoped_lock lock{ref_count_mutex_}; + + // iterate nodes, decrement their ref count and delete if ref count becomes + // zero + while (!queued_nodes.empty()) { + auto [hash, node, depth] = queued_nodes.back(); + queued_nodes.pop_back(); + auto ref_count_it = ref_count_.find(hash); + if (ref_count_it == ref_count_.end()) { + nodes_unknown++; + continue; + } + + auto &ref_count = ref_count_it->second; + BOOST_ASSERT(ref_count != 0); + ref_count--; + SL_TRACE(logger_, + "Prune - {} - Node {}, ref count {}", + depth, + hash, + ref_count); + + if (ref_count == 0) { + nodes_removed++; + ref_count_.erase(ref_count_it); + OUTCOME_TRY(batch.remove(hash)); + auto hash_opt = node->getValue().hash; + if (hash_opt.has_value()) { + auto value_ref_it = value_ref_count_.find(hash); + if (value_ref_it == value_ref_count_.end()) { + values_unknown++; + } else { + auto& value_ref_count = value_ref_it->second; + value_ref_count--; + if (value_ref_count == 0) { + OUTCOME_TRY(batch.remove(hash)); + value_ref_count_.erase(value_ref_it); + values_removed++; + } + } + } + if (node->isBranch()) { + auto branch = static_cast(*node); + for (auto opaque_child : branch.children) { + if (opaque_child != nullptr) { + auto dummy_child = + dynamic_cast(opaque_child.get()); + std::optional child_merkle_value; + if (dummy_child != nullptr) { + child_merkle_value = encoder.getMerkleValue(*dummy_child); + } else { + // used for tests + auto node = + dynamic_cast(opaque_child.get()); + BOOST_ASSERT(node != nullptr); + BOOST_OUTCOME_TRY( + child_merkle_value, + encoder.getMerkleValue(*node, trie::StateVersion::V0)); + } + BOOST_ASSERT(child_merkle_value.has_value()); + if (child_merkle_value->isHash()) { + SL_TRACE(logger_, + "Prune - Child {}", + child_merkle_value->asBuffer()); + OUTCOME_TRY(child, + serializer_->retrieveNode(opaque_child, nullptr)); + queued_nodes.push_back( + {*child_merkle_value->asHash(), child, depth + 1}); + } + } + } + } + } + } + + SL_DEBUG(logger_, "Removed {} nodes", nodes_removed); + if (nodes_unknown > 0) { + SL_WARN(logger_, + "Pruner detected {} unknown nodes during pruning. This indicates " + "a bug.", + nodes_unknown); + } + SL_DEBUG(logger_, "Removed {} values", values_removed); + if (values_unknown > 0) { + SL_WARN(logger_, + "Pruner detected {} unknown nodes during pruning. This indicates " + "a bug.", + values_unknown); + } + + return outcome::success(); + } + + outcome::result TriePrunerImpl::addNewState( + const storage::trie::RootHash &state_root, trie::StateVersion version) { + std::optional> lock; + OUTCOME_TRY(trie, serializer_->retrieveTrie(state_root)); + OUTCOME_TRY(addNewStateWith(*trie, version)); + return outcome::success(); + } + + outcome::result TriePrunerImpl::addNewState( + const trie::PolkadotTrie &new_trie, trie::StateVersion version) { + std::optional> lock; + OUTCOME_TRY(addNewStateWith(new_trie, version)); + return outcome::success(); + } + + outcome::result TriePrunerImpl::addNewStateWith( + const trie::PolkadotTrie &new_trie, trie::StateVersion version) { + if (new_trie.getRoot() == nullptr) { + SL_DEBUG(logger_, "Attempt to add a trie with a null root"); + return outcome::success(); + } + + SL_DEBUG(logger_, "Ref count map size is {}", ref_count_.size()); + KAGOME_PROFILE_START_L(logger_, register_state); + + struct Entry { + std::shared_ptr node; + common::Hash256 hash; + }; + std::vector queued_nodes; + + EncoderCache encoder{*codec_, logger_}; + + OUTCOME_TRY(root_hash, + encoder.getMerkleValue(*new_trie.getRoot(), version)); + BOOST_ASSERT(root_hash.isHash()); + SL_DEBUG(logger_, "Add new state with hash: {}", root_hash.asBuffer()); + queued_nodes.push_back({new_trie.getRoot(), *root_hash.asHash()}); + + size_t referenced_nodes_num = 0; + size_t referenced_values_num = 0; + + while (!queued_nodes.empty()) { + auto [node, hash] = queued_nodes.back(); + queued_nodes.pop_back(); + const size_t ref_count = ++ref_count_[hash]; + SL_TRACE(logger_, "Add node {}, ref count {}", hash.toHex(), ref_count); + + referenced_nodes_num++; + bool is_new_node_with_value = + node != nullptr && ref_count == 1 && node->getValue().is_some(); + if (is_new_node_with_value) { + auto value_hash_opt = encoder.getValueHash(*node, version); + if (value_hash_opt) { + value_ref_count_[*value_hash_opt]++; + referenced_values_num++; + } + } + + bool is_new_branch_node = + node != nullptr && node->isBranch() && ref_count == 1; + if (is_new_branch_node) { + auto branch = static_cast(node.get()); + for (auto opaque_child : branch->children) { + if (opaque_child != nullptr) { + OUTCOME_TRY(child, serializer_->retrieveNode(opaque_child)); + OUTCOME_TRY(child_merkle_val, + encoder.getMerkleValue(*child, version)); + if (child_merkle_val.isHash()) { + SL_TRACE(logger_, "Queue child {}", child_merkle_val.asBuffer()); + queued_nodes.push_back({child, *child_merkle_val.asHash()}); + } + } + } + } + } + OUTCOME_TRY(forEachChildTrie( + new_trie, + [this, version]( + common::BufferView child_key, + const trie::RootHash &child_hash) -> outcome::result { + OUTCOME_TRY(trie, serializer_->retrieveTrie(child_hash)); + OUTCOME_TRY(addNewStateWith(*trie, version)); + return outcome::success(); + })); + SL_DEBUG(logger_, + "Referenced {} nodes and {} values. Ref count map size: {}", + referenced_nodes_num, + referenced_values_num, + ref_count_.size()); + return *root_hash.asHash(); + } + + outcome::result TriePrunerImpl::restoreState( + const primitives::BlockHeader &last_pruned_block, + const blockchain::BlockTree &block_tree) { + KAGOME_PROFILE_START_L(logger_, restore_state); + SL_DEBUG(logger_, + "Restore state - last pruned block #{}", + last_pruned_block.number); + + ref_count_.clear(); + OUTCOME_TRY(last_pruned_enc, scale::encode(last_pruned_block)); + auto last_pruned_hash = hasher_->blake2b_256(last_pruned_enc); + + std::queue block_queue; + + OUTCOME_TRY(last_pruned_children, block_tree.getChildren(last_pruned_hash)); + if (!last_pruned_children.empty()) { + auto &base_block_hash = last_pruned_children.at(0); + OUTCOME_TRY(base_block, block_tree.getBlockHeader(base_block_hash)); + auto base_tree_res = serializer_->retrieveTrie(base_block.state_root); + if (base_tree_res.has_error() + && base_tree_res.error() == storage::DatabaseError::NOT_FOUND) { + SL_DEBUG( + logger_, + "Failed to restore pruner state, probably node is fast-syncing."); + return outcome::success(); + } + OUTCOME_TRY(base_tree, std::move(base_tree_res)); + OUTCOME_TRY(addNewStateWith(*base_tree, trie::StateVersion::V0)); + OUTCOME_TRY(children, block_tree.getChildren(base_block_hash)); + for (auto child : children) { + block_queue.push(child); + } + } + + while (!block_queue.empty()) { + auto block_hash = block_queue.front(); + block_queue.pop(); + + OUTCOME_TRY(header, block_tree.getBlockHeader(block_hash)); + SL_DEBUG(logger_, + "Restore state - register #{} ({})", + header.number, + block_hash); + auto tree_res = serializer_->retrieveTrie(header.state_root); + if (tree_res.has_error() + && tree_res.error() == DatabaseError::NOT_FOUND) { + SL_WARN(logger_, + "State for block #{} is not found in the database", + header.number); + continue; + } + OUTCOME_TRY(tree, tree_res); + OUTCOME_TRY(addNewStateWith(*tree, trie::StateVersion::V0)); + + OUTCOME_TRY(children, block_tree.getChildren(block_hash)); + for (auto child : children) { + block_queue.push(child); + } + } + last_pruned_block_ = {last_pruned_hash, last_pruned_block.number}; + OUTCOME_TRY(savePersistentState()); + return outcome::success(); + } + + outcome::result TriePrunerImpl::savePersistentState() const { + OUTCOME_TRY(enc_info, + scale::encode(TriePrunerInfo{ + last_pruned_block_, + })); + BOOST_ASSERT(storage_->getSpace(kDefault)); + OUTCOME_TRY(storage_->getSpace(kDefault)->put( + TRIE_PRUNER_INFO_KEY, common::Buffer{std::move(enc_info)})); + return outcome::success(); + } + +} // namespace kagome::storage::trie_pruner diff --git a/core/storage/trie_pruner/impl/trie_pruner_impl.hpp b/core/storage/trie_pruner/impl/trie_pruner_impl.hpp new file mode 100644 index 0000000000..bcd0593109 --- /dev/null +++ b/core/storage/trie_pruner/impl/trie_pruner_impl.hpp @@ -0,0 +1,150 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_TRIE_PRUNER_IMPL_HPP +#define KAGOME_TRIE_PRUNER_IMPL_HPP + +#include "storage/trie_pruner/trie_pruner.hpp" + +#include +#include +#include +#include + +#include + +#include "scale/tie.hpp" + +#include "common/buffer.hpp" +#include "log/logger.hpp" +#include "log/profiling_logger.hpp" +#include "storage/buffer_map_types.hpp" + +namespace kagome::application { + class AppConfiguration; + class AppStateManager; +} // namespace kagome::application + +namespace kagome::crypto { + class Hasher; +} + +namespace kagome::blockchain { + class BlockTree; + class BlockStorage; +} // namespace kagome::blockchain + +namespace kagome::storage { + class SpacedStorage; +} + +namespace kagome::storage::trie { + class TrieStorageBackend; + class TrieSerializer; + class Codec; +} // namespace kagome::storage::trie + +namespace kagome::storage::trie_pruner { + + using common::literals::operator""_buf; + + class TriePrunerImpl final : public TriePruner { + public: + enum class Error { + LAST_PRUNED_BLOCK_IS_LAST_FINALIZED = 1, + }; + + inline static const common::Buffer TRIE_PRUNER_INFO_KEY = + ":trie_pruner:info"_buf; + + struct TriePrunerInfo { + SCALE_TIE(1); + + std::optional last_pruned_block; + }; + + TriePrunerImpl( + std::shared_ptr app_state_manager, + std::shared_ptr trie_storage, + std::shared_ptr serializer, + std::shared_ptr codec, + std::shared_ptr storage, + std::shared_ptr hasher, + std::shared_ptr config); + + bool prepare(); + + virtual outcome::result addNewState( + const storage::trie::RootHash &state_root, + trie::StateVersion version) override; + + virtual outcome::result addNewState( + const trie::PolkadotTrie &new_trie, + trie::StateVersion version) override; + + virtual outcome::result pruneFinalized( + const primitives::BlockHeader &state) override; + + virtual outcome::result pruneDiscarded( + const primitives::BlockHeader &state) override; + + virtual outcome::result restoreState( + const primitives::BlockHeader &last_pruned_block, + const blockchain::BlockTree &block_tree) override; + + std::optional getLastPrunedBlock() const override { + return last_pruned_block_; + } + + size_t getTrackedNodesNum() const { + return ref_count_.size(); + } + + size_t getRefCountOf(const common::Hash256 &node) const { + auto it = ref_count_.find(node); + return it == ref_count_.end() ? 0 : it->second; + } + + template + void forRefCounts(const F &f) { + for (auto &[node, count] : ref_count_) { + f(node, count); + } + } + + std::optional getPruningDepth() const override { + return pruning_depth_; + } + + private: + outcome::result prune(BufferBatch &batch, + const storage::trie::RootHash &state); + + outcome::result addNewStateWith( + const trie::PolkadotTrie &new_trie, trie::StateVersion version); + + // store the persistent pruner info to the database + outcome::result savePersistentState() const; + + std::mutex ref_count_mutex_; + std::unordered_map ref_count_; + std::unordered_map value_ref_count_; + + std::optional last_pruned_block_; + std::shared_ptr trie_storage_; + std::shared_ptr serializer_; + std::shared_ptr codec_; + std::shared_ptr storage_; + std::shared_ptr hasher_; + + const std::optional pruning_depth_{}; + log::Logger logger_ = log::createLogger("TriePruner", "trie_pruner"); + }; + +} // namespace kagome::storage::trie_pruner + +OUTCOME_HPP_DECLARE_ERROR(kagome::storage::trie_pruner, TriePrunerImpl::Error); + +#endif // KAGOME_TRIE_PRUNER_IMPL_HPP diff --git a/core/storage/trie_pruner/recover_pruner_state.hpp b/core/storage/trie_pruner/recover_pruner_state.hpp new file mode 100644 index 0000000000..35b9307bd6 --- /dev/null +++ b/core/storage/trie_pruner/recover_pruner_state.hpp @@ -0,0 +1,24 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_RECOVER_PRUNER_STATE_HPP +#define KAGOME_RECOVER_PRUNER_STATE_HPP + +#include "outcome/outcome.hpp" + +namespace kagome::blockchain { + class BlockTree; +} + +namespace kagome::storage::trie_pruner { + + class TriePruner; + + outcome::result recoverPrunerState( + TriePruner &pruner, const blockchain::BlockTree &block_tree); + +} + +#endif // KAGOME_RECOVER_PRUNER_STATE_HPP diff --git a/core/storage/trie_pruner/trie_pruner.hpp b/core/storage/trie_pruner/trie_pruner.hpp new file mode 100644 index 0000000000..5555df46a8 --- /dev/null +++ b/core/storage/trie_pruner/trie_pruner.hpp @@ -0,0 +1,95 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_TRIEPRUNER_HPP +#define KAGOME_TRIEPRUNER_HPP + +#include + +#include "common/buffer.hpp" +#include "primitives/block_header.hpp" +#include "primitives/block_id.hpp" +#include "storage/trie/types.hpp" + +namespace kagome::blockchain { + class BlockTree; +} + +namespace kagome::storage::trie { + class PolkadotTrie; +} + +namespace kagome::storage::trie_pruner { + + /** + * Pruner is responsible for removal of trie storage parts belonging to + * old or discarded blocks from the database. It works in real-time. + */ + class TriePruner { + public: + virtual ~TriePruner() = default; + + /** + * Register a new trie with the trie pruner so that the trie nodes + * this trie references are kept until the block this trie belongs to is + * pruned. + * @param version trie version used by runtime when creating this trie. + */ + virtual outcome::result addNewState( + const storage::trie::RootHash &state_root, + trie::StateVersion version) = 0; + + /** + * Register a new trie with the trie pruner so that the trie nodes + * this trie references are kept until the block this trie belongs to is + * pruned. + * @note This overload avoids downloading trie nodes that are already in + * memory from the database + * @param version trie version used by runtime when creating this trie. + */ + virtual outcome::result addNewState( + const trie::PolkadotTrie &new_trie, trie::StateVersion version) = 0; + + /** + * Prune the trie of a finalized block \param state. + * Nodes belonging to this trie are deleted if no other trie references + * them. + * @param state header of the block which state is to be pruned. + */ + virtual outcome::result pruneFinalized( + const primitives::BlockHeader &state) = 0; + + /** + * Prune the trie of a discarded block \param state. + * Nodes belonging to this trie are deleted if no other trie references + * them. + * @param state header of the block which state is to be pruned. + */ + virtual outcome::result pruneDiscarded( + const primitives::BlockHeader &state) = 0; + + /** + * Resets the pruner state, sets the provided block as the last pruned, + * adds all its children states to the pruner reference counter + */ + virtual outcome::result restoreState( + const primitives::BlockHeader &last_pruned_block, + const blockchain::BlockTree &block_tree) = 0; + + /** + * @return the last pruned block. + */ + virtual std::optional getLastPrunedBlock() const = 0; + + /** + * @return the number of blocks behind the last finalized one + * which states should be kept. + */ + virtual std::optional getPruningDepth() const = 0; + }; + +} // namespace kagome::storage::trie_pruner + +#endif // KAGOME_TRIEPRUNER_HPP diff --git a/core/telemetry/impl/service_impl.cpp b/core/telemetry/impl/service_impl.cpp index 3c7efd03d4..c76a94cafc 100644 --- a/core/telemetry/impl/service_impl.cpp +++ b/core/telemetry/impl/service_impl.cpp @@ -402,7 +402,7 @@ namespace kagome::telemetry { height.SetInt(last_imported_.block.number); finalized_height.SetInt(last_finalized_.block.number); tx_count.SetInt(tx_pool_->getStatus().ready_num); - state_size.SetInt(buffer_storage_->size()); + state_size.SetInt(buffer_storage_->byteSizeHint().value_or(0)); best_hash = str_val(fmt::format("{:l}", last_imported_.block.hash)); finalized_hash = str_val(fmt::format("{:l}", last_finalized_.block.hash)); } diff --git a/core/utils/CMakeLists.txt b/core/utils/CMakeLists.txt index c90c7a62da..86dbdebe6f 100644 --- a/core/utils/CMakeLists.txt +++ b/core/utils/CMakeLists.txt @@ -24,7 +24,6 @@ target_link_libraries(kagome-db-editor hasher storage runtime_upgrade_tracker - storage ) if (BACKWARD) add_backward(kagome-db-editor) diff --git a/core/utils/kagome_db_editor.cpp b/core/utils/kagome_db_editor.cpp index a1e044b982..4446d67fbb 100644 --- a/core/utils/kagome_db_editor.cpp +++ b/core/utils/kagome_db_editor.cpp @@ -31,6 +31,7 @@ #include "storage/trie/polkadot_trie/polkadot_trie_factory_impl.hpp" #include "storage/trie/serialization/polkadot_codec.hpp" #include "storage/trie/serialization/trie_serializer_impl.hpp" +#include "storage/trie_pruner/impl/trie_pruner_impl.hpp" #include "utils/profiler.hpp" namespace di = boost::di; @@ -257,6 +258,8 @@ int db_editor_main(int argc, const char **argv) { injector.template create>()); }), di::bind.template to(trie_tracker), + di::bind.template to( + std::shared_ptr(nullptr)), di::bind.template to(), di::bind.to(factory), di::bind.template to(), @@ -360,10 +363,12 @@ int db_editor_main(int argc, const char **argv) { .value(); } - auto trie = TrieStorageImpl::createFromStorage( - injector.template create>(), - injector.template create>()) - .value(); + auto trie = + TrieStorageImpl::createFromStorage( + injector.template create>(), + injector.template create>(), + injector.template create>()) + .value(); if (COMPACT == cmd) { auto batch = check(persistent_batch(trie, target_state)).value(); diff --git a/core/utils/safe_object.hpp b/core/utils/safe_object.hpp index fd6a68f896..84f742ea64 100644 --- a/core/utils/safe_object.hpp +++ b/core/utils/safe_object.hpp @@ -53,6 +53,14 @@ struct SafeObject { return std::forward(f)(t_); } + T& unsafeGet() { + return t_; + } + + const T& unsafeGet() const { + return t_; + } + private: T t_; mutable M cs_; diff --git a/node/CMakeLists.txt b/node/CMakeLists.txt index 75e4bf6225..bd3f1e2a34 100644 --- a/node/CMakeLists.txt +++ b/node/CMakeLists.txt @@ -1,11 +1,23 @@ # # Copyright Soramitsu Co., Ltd. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -# -add_executable(kagome - main.cpp - ) +# fix for dyld bug on mac +# we should actually check the target platform here, not the host platform, +# but not that we explicitly support cross-compilation anyway +if (CMAKE_HOST_APPLE AND CMAKE_BUILD_TYPE STREQUAL "Debug") + add_library(kagome SHARED + main.cpp + ) + + set_target_properties(kagome PROPERTIES PREFIX "" DEBUG_POSTFIX "") + add_executable(kagome_dlopen dlopen.c) + set_target_properties(kagome_dlopen PROPERTIES OUTPUT_NAME kagome) + +else () + add_executable(kagome main.cpp) +endif () + target_link_libraries(kagome Boost::boost Boost::program_options @@ -21,5 +33,4 @@ target_link_libraries(kagome ) if (BACKWARD) add_backward(kagome) -endif() -kagome_install(kagome) +endif () diff --git a/node/dlopen.c b/node/dlopen.c new file mode 100644 index 0000000000..74da8e5aeb --- /dev/null +++ b/node/dlopen.c @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +int main(int argc, char **argv) { + const char *suffix = ".dylib"; + char *dylib = (char *)malloc(strlen(argv[0]) + strlen(suffix) + 1); + strcpy(dylib, argv[0]); + strcpy(dylib + strlen(argv[0]), suffix); + + void *lib = dlopen(dylib, RTLD_NOW); + if (lib == NULL) { + printf("dlopen: %s\n", dlerror()); + return -1; + } + void *sym = dlsym(lib, "main"); + if (sym == NULL) { + printf("dlsym: %s\n", dlerror()); + return -1; + } + return ((int (*)(int, char **))sym)(argc, argv); +} diff --git a/test/core/blockchain/block_tree_test.cpp b/test/core/blockchain/block_tree_test.cpp index 4e5bf10c70..c605cfe858 100644 --- a/test/core/blockchain/block_tree_test.cpp +++ b/test/core/blockchain/block_tree_test.cpp @@ -22,6 +22,7 @@ #include "mock/core/consensus/babe/babe_config_repository_mock.hpp" #include "mock/core/consensus/babe/babe_util_mock.hpp" #include "mock/core/runtime/core_mock.hpp" +#include "mock/core/storage/trie_pruner/trie_pruner_mock.hpp" #include "mock/core/transaction_pool/transaction_pool_mock.hpp" #include "network/impl/extrinsic_observer_impl.hpp" #include "primitives/block_id.hpp" @@ -135,6 +136,15 @@ struct BlockTreeTest : public testing::Test { return outcome::success(); })); + ON_CALL(*state_pruner_, restoreState(_, _)) + .WillByDefault(Return(outcome::success())); + + ON_CALL(*state_pruner_, pruneDiscarded(_)) + .WillByDefault(Return(outcome::success())); + + ON_CALL(*state_pruner_, pruneFinalized(_)) + .WillByDefault(Return(outcome::success())); + putNumToHash(kGenesisBlockInfo); putNumToHash(kFinalizedBlockInfo); @@ -155,6 +165,7 @@ struct BlockTreeTest : public testing::Test { ext_events_engine, extrinsic_event_key_repo, justification_storage_policy_, + state_pruner_, std::make_shared<::boost::asio::io_context>()) .value(); } @@ -246,6 +257,9 @@ struct BlockTreeTest : public testing::Test { justification_storage_policy_ = std::make_shared>(); + std::shared_ptr state_pruner_ = + std::make_shared(); + std::shared_ptr app_state_manager_ = std::make_shared(); diff --git a/test/core/network/state_protocol_observer_test.cpp b/test/core/network/state_protocol_observer_test.cpp index c231ec4812..780dbb96c3 100644 --- a/test/core/network/state_protocol_observer_test.cpp +++ b/test/core/network/state_protocol_observer_test.cpp @@ -9,6 +9,7 @@ #include #include "mock/core/blockchain/block_header_repository_mock.hpp" +#include "mock/core/storage/trie_pruner/trie_pruner_mock.hpp" #include "network/types/state_request.hpp" #include "storage/in_memory/in_memory_storage.hpp" #include "storage/trie/impl/trie_storage_backend_impl.hpp" @@ -31,6 +32,10 @@ using namespace primitives; using namespace storage; using namespace trie; +using namespace trie_pruner; + +using testing::_; +using testing::Return; std::shared_ptr makeEmptyInMemoryTrie() { auto backend = @@ -41,9 +46,13 @@ std::shared_ptr makeEmptyInMemoryTrie() { auto codec = std::make_shared(); auto serializer = std::make_shared(trie_factory, codec, backend); + auto state_pruner = std::make_shared(); + ON_CALL(*state_pruner, + addNewState(testing::A(), _)) + .WillByDefault(Return(outcome::success())); return kagome::storage::trie::TrieStorageImpl::createEmpty( - trie_factory, codec, serializer) + trie_factory, codec, serializer, state_pruner) .value(); } @@ -121,11 +130,7 @@ TEST_F(StateProtocolObserverTest, Simple) { EXPECT_CALL(*headers_, getBlockHeader({"1"_hash256})) .WillRepeatedly(testing::Return(header)); - StateRequest request{ - .hash = "1"_hash256, - .start = {}, - .no_proof = false - }; + StateRequest request{.hash = "1"_hash256, .start = {}, .no_proof = false}; EXPECT_OUTCOME_TRUE(response, state_protocol_observer_->onStateRequest(request)); diff --git a/test/core/network/synchronizer_test.cpp b/test/core/network/synchronizer_test.cpp index 569857b7e9..f1d2ef1349 100644 --- a/test/core/network/synchronizer_test.cpp +++ b/test/core/network/synchronizer_test.cpp @@ -22,6 +22,8 @@ #include "mock/core/runtime/core_mock.hpp" #include "mock/core/runtime/module_factory_mock.hpp" #include "mock/core/storage/persistent_map_mock.hpp" +#include "mock/core/storage/spaced_storage_mock.hpp" +#include "mock/core/storage/trie_pruner/trie_pruner_mock.hpp" #include "mock/core/storage/trie/serialization/trie_serializer_mock.hpp" #include "mock/core/storage/trie/trie_storage_mock.hpp" #include "network/impl/synchronizer_impl.hpp" @@ -77,6 +79,9 @@ class SynchronizerTest EXPECT_CALL(app_config, syncMethod()) .WillOnce(Return(application::AppConfiguration::SyncMethod::Full)); + auto state_pruner = + std::make_shared(); + synchronizer = std::make_shared(app_config, app_state_manager, @@ -86,6 +91,7 @@ class SynchronizerTest block_executor, serializer, storage, + state_pruner, router, scheduler, hasher, diff --git a/test/core/runtime/trie_storage_provider_test.cpp b/test/core/runtime/trie_storage_provider_test.cpp index e328407109..b5392c70e3 100644 --- a/test/core/runtime/trie_storage_provider_test.cpp +++ b/test/core/runtime/trie_storage_provider_test.cpp @@ -8,6 +8,7 @@ #include "runtime/common/trie_storage_provider_impl.hpp" #include "common/buffer.hpp" +#include "mock/core/storage/trie_pruner/trie_pruner_mock.hpp" #include "runtime/common/runtime_transaction_error.hpp" #include "storage/in_memory/in_memory_storage.hpp" #include "storage/trie/impl/trie_storage_backend_impl.hpp" @@ -44,9 +45,13 @@ class TrieStorageProviderTest : public ::testing::Test { std::make_shared( trie_factory, codec, backend); - auto trieDb = kagome::storage::trie::TrieStorageImpl::createEmpty( - trie_factory, codec, serializer) - .value(); + auto state_pruner = + std::make_shared(); + + auto trieDb = + kagome::storage::trie::TrieStorageImpl::createEmpty( + trie_factory, codec, serializer, state_pruner) + .value(); storage_provider_ = std::make_shared( diff --git a/test/core/runtime/wavm/wasm_executor_test.cpp b/test/core/runtime/wavm/wasm_executor_test.cpp index 68681ac293..c60648b519 100644 --- a/test/core/runtime/wavm/wasm_executor_test.cpp +++ b/test/core/runtime/wavm/wasm_executor_test.cpp @@ -23,6 +23,7 @@ #include "mock/core/offchain/offchain_worker_pool_mock.hpp" #include "mock/core/runtime/runtime_properties_cache_mock.hpp" #include "mock/core/runtime/runtime_upgrade_tracker_mock.hpp" +#include "mock/core/storage/trie_pruner/trie_pruner_mock.hpp" #include "runtime/common/executor.hpp" #include "runtime/common/module_repository_impl.hpp" #include "runtime/common/runtime_instances_pool.hpp" @@ -78,6 +79,7 @@ using kagome::storage::trie::PolkadotTrieImpl; using kagome::storage::trie::TrieSerializerImpl; using kagome::storage::trie::TrieStorage; using kagome::storage::trie::TrieStorageImpl; +using kagome::storage::trie_pruner::TriePrunerMock; using testing::_; using testing::Invoke; using testing::Return; @@ -105,10 +107,11 @@ class WasmExecutorTest : public ::testing::Test { auto codec = std::make_shared(); auto serializer = std::make_shared(trie_factory, codec, backend); + auto state_pruner = std::make_shared(); std::shared_ptr trie_db = kagome::storage::trie::TrieStorageImpl::createEmpty( - trie_factory, codec, serializer) + trie_factory, codec, serializer, state_pruner) .value(); storage_provider_ = diff --git a/test/core/scale/CMakeLists.txt b/test/core/scale/CMakeLists.txt index 9eca91c174..516a8ea526 100644 --- a/test/core/scale/CMakeLists.txt +++ b/test/core/scale/CMakeLists.txt @@ -9,4 +9,4 @@ addtest(big_fixed_integers_test target_link_libraries(big_fixed_integers_test scale::scale hexutil - ) \ No newline at end of file + ) diff --git a/test/core/storage/CMakeLists.txt b/test/core/storage/CMakeLists.txt index 9d6603367c..734e4c1199 100644 --- a/test/core/storage/CMakeLists.txt +++ b/test/core/storage/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory(trie) add_subdirectory(rocksdb) add_subdirectory(changes_trie) +add_subdirectory(trie_pruner) diff --git a/test/core/storage/changes_trie/changes_tracker_test.cpp b/test/core/storage/changes_trie/changes_tracker_test.cpp index fd49ce93f1..fd52a0dff3 100644 --- a/test/core/storage/changes_trie/changes_tracker_test.cpp +++ b/test/core/storage/changes_trie/changes_tracker_test.cpp @@ -8,6 +8,7 @@ #include "storage/changes_trie/impl/storage_changes_tracker_impl.hpp" #include "mock/core/blockchain/block_header_repository_mock.hpp" +#include "mock/core/storage/trie_pruner/trie_pruner_mock.hpp" #include "primitives/event_types.hpp" #include "scale/scale.hpp" #include "storage/in_memory/in_memory_storage.hpp" @@ -35,6 +36,7 @@ using kagome::storage::trie::PolkadotCodec; using kagome::storage::trie::PolkadotTrieFactoryImpl; using kagome::storage::trie::TrieSerializerImpl; using kagome::storage::trie::TrieStorageBackendImpl; +using kagome::storage::trie_pruner::TriePrunerMock; using testing::_; using testing::AnyOf; using testing::Return; @@ -63,7 +65,9 @@ TEST(ChangesTrieTest, IntegrationWithOverlay) { codec, serializer, std::make_optional(changes_tracker), - factory->createEmpty([](auto &) { return outcome::success(); })); + factory->createEmpty({[](auto &) { return outcome::success(); }, + [](auto &) { return outcome::success(); }}), + std::make_shared()); EXPECT_OUTCOME_TRUE_1( batch->put(":extrinsic_index"_buf, Buffer{scale::encode(42).value()})); diff --git a/test/core/storage/trie/polkadot_trie/polkadot_trie_cursor_test.cpp b/test/core/storage/trie/polkadot_trie/polkadot_trie_cursor_test.cpp index 210c0d64ed..755b3313d0 100644 --- a/test/core/storage/trie/polkadot_trie/polkadot_trie_cursor_test.cpp +++ b/test/core/storage/trie/polkadot_trie/polkadot_trie_cursor_test.cpp @@ -43,7 +43,7 @@ std::tuple, std::set> generateRandomTrie( size_t max_key_length = 32, size_t key_alphabet_size = 16) noexcept { std::tuple, std::set> res; - auto trie = std::make_shared(); + auto trie = PolkadotTrieImpl::createEmpty(); std::mt19937 eng(5489u); // explicitly set default seed std::uniform_int_distribution key_dist( 0, key_alphabet_size); @@ -65,7 +65,7 @@ std::tuple, std::set> generateRandomTrie( std::shared_ptr makeTrie( const std::vector> &vals) { - auto trie = std::make_shared(); + auto trie = PolkadotTrieImpl::createEmpty(); for (auto &p : vals) { EXPECT_OUTCOME_TRUE_1(trie->put(p.first, BufferView{p.second})); } diff --git a/test/core/storage/trie/polkadot_trie/polkadot_trie_test.cpp b/test/core/storage/trie/polkadot_trie/polkadot_trie_test.cpp index b01f264a23..a878e16e4e 100644 --- a/test/core/storage/trie/polkadot_trie/polkadot_trie_test.cpp +++ b/test/core/storage/trie/polkadot_trie/polkadot_trie_test.cpp @@ -43,12 +43,12 @@ class TrieTest void SetUp() override { testutil::prepareLoggers(soralog::Level::OFF); - trie = std::make_unique(); + trie = PolkadotTrieImpl::createEmpty(); } static const std::vector> data; - std::unique_ptr trie; + std::shared_ptr trie; }; const std::vector> TrieTest::data = { @@ -365,10 +365,10 @@ class DeleteTest : public testing::Test, DeleteTest() {} void SetUp() override { - trie = std::make_unique(); + trie = PolkadotTrieImpl::createEmpty(); } - std::unique_ptr trie; + std::shared_ptr trie; }; size_t size(const PolkadotTrie::NodePtr &node) { @@ -432,10 +432,10 @@ class ClearPrefixTest : public testing::Test, ClearPrefixTest() {} void SetUp() override { - trie = std::make_unique(); + trie = PolkadotTrieImpl::createEmpty(); } - std::unique_ptr trie; + std::shared_ptr trie; }; /** @@ -543,13 +543,13 @@ TEST_F(TrieTest, GetPath) { } std::vector> path; - ASSERT_OUTCOME_SUCCESS_TRY( - trie->forNodeInPath(trie->getRoot(), - KeyNibbles{"010203040506"_hex2buf}, - [&path](const auto &node, auto idx) mutable { - path.emplace_back(&node, idx); - return outcome::success(); - })) + ASSERT_OUTCOME_SUCCESS_TRY(trie->forNodeInPath( + trie->getRoot(), + KeyNibbles{"010203040506"_hex2buf}, + [&path](const auto &node, auto idx, auto &child) mutable { + path.emplace_back(&node, idx); + return outcome::success(); + })) auto root = trie->getRoot(); auto node1 = trie->getNode(root, KeyNibbles{1, 2, 3, 4}).value(); auto it = path.begin(); @@ -577,10 +577,11 @@ TEST_F(TrieTest, GetPathToInvalid) { } EXPECT_OUTCOME_SOME_ERROR( _, - trie->forNodeInPath( - trie->getRoot(), - KeyNibbles{"0a0b0c0d0e0f"_hex2buf}, - [](auto &node, auto idx) mutable { return outcome::success(); })) + trie->forNodeInPath(trie->getRoot(), + KeyNibbles{"0a0b0c0d0e0f"_hex2buf}, + [](auto &node, auto idx, auto &child) mutable { + return outcome::success(); + })) } /** @@ -603,5 +604,5 @@ TEST_F(TrieTest, GetNodeReturnsNullptrWhenNotFound) { ASSERT_OUTCOME_SUCCESS( res, trie->getNode(trie->getRoot(), KeyNibbles{"01020304050607"_hex2buf})); - ASSERT_EQ(res, nullptr) << res->value.value->toHex(); + ASSERT_EQ(res, nullptr) << res->getValue().value->toHex(); } diff --git a/test/core/storage/trie/trie_storage/polkadot_codec_hash256_test.cpp b/test/core/storage/trie/trie_storage/polkadot_codec_hash256_test.cpp index f239752c3e..ecc23e2b7a 100644 --- a/test/core/storage/trie/trie_storage/polkadot_codec_hash256_test.cpp +++ b/test/core/storage/trie/trie_storage/polkadot_codec_hash256_test.cpp @@ -22,7 +22,7 @@ TEST_P(Hash256Test, Valid) { auto [in, out] = GetParam(); auto codec = std::make_unique(); auto actualOut = codec->merkleValue(in); - EXPECT_EQ(actualOut.toHex(), out.toHex()); + EXPECT_EQ(actualOut.asBuffer().toHex(), out.toHex()); } Buffer getBlake2b(const Buffer &in) { diff --git a/test/core/storage/trie/trie_storage/polkadot_codec_node_decoding_test.cpp b/test/core/storage/trie/trie_storage/polkadot_codec_node_decoding_test.cpp index 59c5027a24..eea272ca04 100644 --- a/test/core/storage/trie/trie_storage/polkadot_codec_node_decoding_test.cpp +++ b/test/core/storage/trie/trie_storage/polkadot_codec_node_decoding_test.cpp @@ -30,16 +30,16 @@ TEST_P(NodeDecodingTest, GetHeader) { encoded, codec->encodeNode(*node, storage::trie::StateVersion::V0, {})); EXPECT_OUTCOME_TRUE(decoded, codec->decodeNode(encoded)); auto decoded_node = std::dynamic_pointer_cast(decoded); - EXPECT_EQ(decoded_node->key_nibbles, node->key_nibbles); - EXPECT_EQ(decoded_node->value, node->value); + EXPECT_EQ(decoded_node->getKeyNibbles(), node->getKeyNibbles()); + EXPECT_EQ(decoded_node->getValue(), node->getValue()); } template std::shared_ptr make(const common::Buffer &key_nibbles, const common::Buffer &value) { auto node = std::make_shared(); - node->key_nibbles = key_nibbles; - node->value.value = value; + node->setKeyNibbles(key_nibbles); + node->getMutableValue().value = value; return node; } @@ -57,10 +57,10 @@ std::shared_ptr branch_with_2_children = []() { using T = TrieNode::Type; -static const std::vector> CASES = { +static const std::vector> DECODING_CASES = { make("010203"_hex2buf, "abcdef"_hex2buf), make("0a0b0c"_hex2buf, "abcdef"_hex2buf), make("010203"_hex2buf, "abcdef"_hex2buf), branch_with_2_children}; -INSTANTIATE_TEST_SUITE_P(PolkadotCodec, NodeDecodingTest, ValuesIn(CASES)); +INSTANTIATE_TEST_SUITE_P(PolkadotCodec, NodeDecodingTest, ValuesIn(DECODING_CASES)); diff --git a/test/core/storage/trie/trie_storage/polkadot_codec_node_encoding_test.cpp b/test/core/storage/trie/trie_storage/polkadot_codec_node_encoding_test.cpp index e6e2f0b244..7643a611ec 100644 --- a/test/core/storage/trie/trie_storage/polkadot_codec_node_encoding_test.cpp +++ b/test/core/storage/trie/trie_storage/polkadot_codec_node_encoding_test.cpp @@ -37,8 +37,8 @@ template std::shared_ptr make(const common::Buffer &key_nibbles, std::optional value) { auto node = std::make_shared(); - node->key_nibbles = key_nibbles; - node->value.value = value; + node->setKeyNibbles(key_nibbles); + node->getMutableValue().value = value; return node; } diff --git a/test/core/storage/trie/trie_storage/trie_batch_test.cpp b/test/core/storage/trie/trie_storage/trie_batch_test.cpp index 80121aa37e..0a827b80e6 100644 --- a/test/core/storage/trie/trie_storage/trie_batch_test.cpp +++ b/test/core/storage/trie/trie_storage/trie_batch_test.cpp @@ -6,6 +6,7 @@ #include #include +#include "mock/core/storage/trie_pruner/trie_pruner_mock.hpp" #include "storage/changes_trie/impl/storage_changes_tracker_impl.hpp" #include "storage/in_memory/in_memory_storage.hpp" #include "storage/trie/impl/topper_trie_batch_impl.hpp" @@ -28,6 +29,7 @@ using kagome::common::Hash256; using kagome::primitives::BlockHash; using kagome::storage::Space; using kagome::storage::trie::StateVersion; +using kagome::storage::trie_pruner::TriePrunerMock; using kagome::subscription::SubscriptionEngine; using testing::_; using testing::Invoke; @@ -52,7 +54,15 @@ class TrieBatchTest : public test::BaseRocksDB_Test { empty_hash = serializer->getEmptyRootHash(); - trie = TrieStorageImpl::createEmpty(factory, codec, serializer).value(); + auto state_pruner = std::make_shared(); + ON_CALL(*state_pruner, + addNewState( + testing::A(), _)) + .WillByDefault(Return(outcome::success())); + + trie = + TrieStorageImpl::createEmpty(factory, codec, serializer, state_pruner) + .value(); } static const std::vector> data; @@ -195,7 +205,15 @@ TEST_F(TrieBatchTest, ConsistentOnFailure) { auto codec = std::make_shared(); auto serializer = std::make_shared( factory, codec, std::make_shared(std::move(db))); - auto trie = TrieStorageImpl::createEmpty(factory, codec, serializer).value(); + auto state_pruner = std::make_shared(); + ON_CALL( + *state_pruner, + addNewState(testing::A(), _)) + .WillByDefault(Return(outcome::success())); + + auto trie = + TrieStorageImpl::createEmpty(factory, codec, serializer, state_pruner) + .value(); auto batch = trie->getPersistentBatchAt(empty_hash, std::nullopt).value(); ASSERT_OUTCOME_SUCCESS_TRY(batch->put("123"_buf, "111"_buf)); diff --git a/test/core/storage/trie/trie_storage/trie_storage_test.cpp b/test/core/storage/trie/trie_storage/trie_storage_test.cpp index 45e8c1f429..0d5a9ab247 100644 --- a/test/core/storage/trie/trie_storage/trie_storage_test.cpp +++ b/test/core/storage/trie/trie_storage/trie_storage_test.cpp @@ -8,6 +8,7 @@ #include #include "filesystem/common.hpp" +#include "mock/core/storage/trie_pruner/trie_pruner_mock.hpp" #include "outcome/outcome.hpp" #include "storage/rocksdb/rocksdb.hpp" #include "storage/trie/impl/trie_storage_backend_impl.hpp" @@ -30,12 +31,15 @@ using kagome::storage::trie::StateVersion; using kagome::storage::trie::TrieSerializerImpl; using kagome::storage::trie::TrieStorageBackendImpl; using kagome::storage::trie::TrieStorageImpl; +using kagome::storage::trie_pruner::TriePrunerMock; using kagome::subscription::SubscriptionEngine; +using testing::_; +using testing::Return; /** * @given an empty persistent trie with RocksDb backend - * @when putting a value into it @and its intance is destroyed @and a new - * instance initialsed with the same DB + * @when putting a value into it @and its instance is destroyed @and a new + * instance initialised with the same DB * @then the new instance contains the same data */ TEST(TriePersistencyTest, CreateDestroyCreate) { @@ -57,8 +61,15 @@ TEST(TriePersistencyTest, CreateDestroyCreate) { std::make_shared( rocks_db->getSpace(Space::kDefault))); + auto state_pruner = std::make_shared(); + ON_CALL(*state_pruner, + addNewState( + testing::A(), _)) + .WillByDefault(Return(outcome::success())); + auto storage = - TrieStorageImpl::createEmpty(factory, codec, serializer).value(); + TrieStorageImpl::createEmpty(factory, codec, serializer, state_pruner) + .value(); auto batch = storage @@ -77,7 +88,10 @@ TEST(TriePersistencyTest, CreateDestroyCreate) { codec, std::make_shared( new_rocks_db->getSpace(Space::kDefault))); - auto storage = TrieStorageImpl::createFromStorage(codec, serializer).value(); + auto state_pruner = std::make_shared(); + auto storage = + TrieStorageImpl::createFromStorage(codec, serializer, state_pruner) + .value(); auto batch = storage->getPersistentBatchAt(root, std::nullopt).value(); EXPECT_OUTCOME_TRUE(v1, batch->get("123"_buf)); ASSERT_EQ(v1, "abc"_buf); diff --git a/test/core/storage/trie_pruner/CMakeLists.txt b/test/core/storage/trie_pruner/CMakeLists.txt new file mode 100644 index 0000000000..c7e8b7d1a4 --- /dev/null +++ b/test/core/storage/trie_pruner/CMakeLists.txt @@ -0,0 +1,3 @@ + +addtest(trie_pruner_test trie_pruner_test.cpp) +target_link_libraries(trie_pruner_test storage logger log_configurator hasher) diff --git a/test/core/storage/trie_pruner/trie_pruner_test.cpp b/test/core/storage/trie_pruner/trie_pruner_test.cpp new file mode 100644 index 0000000000..8a5cdebb2d --- /dev/null +++ b/test/core/storage/trie_pruner/trie_pruner_test.cpp @@ -0,0 +1,842 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include "crypto/hasher/hasher_impl.hpp" +#include "mock/core/application/app_configuration_mock.hpp" +#include "mock/core/application/app_state_manager_mock.hpp" +#include "mock/core/blockchain/block_tree_mock.hpp" +#include "mock/core/storage/persistent_map_mock.hpp" +#include "mock/core/storage/spaced_storage_mock.hpp" +#include "mock/core/storage/trie/polkadot_trie_cursor_mock.h" +#include "mock/core/storage/trie/serialization/codec_mock.hpp" +#include "mock/core/storage/trie/serialization/trie_serializer_mock.hpp" +#include "mock/core/storage/trie/trie_storage_backend_mock.hpp" +#include "mock/core/storage/write_batch_mock.hpp" +#include "storage/database_error.hpp" +#include "storage/trie/polkadot_trie/polkadot_trie_factory_impl.hpp" +#include "storage/trie/polkadot_trie/polkadot_trie_impl.hpp" +#include "storage/trie/serialization/polkadot_codec.hpp" +#include "storage/trie/serialization/trie_serializer_impl.hpp" +#include "storage/trie_pruner/impl/trie_pruner_impl.hpp" +#include "storage/trie_pruner/recover_pruner_state.hpp" +#include "testutil/literals.hpp" +#include "testutil/outcome.hpp" +#include "testutil/prepare_loggers.hpp" +#include "testutil/storage/polkadot_trie_printer.hpp" + +using namespace kagome::storage; +using namespace kagome::storage::trie_pruner; +using namespace kagome::common; +namespace primitives = kagome::primitives; +namespace crypto = kagome::crypto; +using kagome::primitives::BlockHash; +using kagome::primitives::BlockHeader; +using kagome::primitives::BlockInfo; +using kagome::primitives::BlockNumber; +using testing::_; +using testing::A; +using testing::An; +using testing::Invoke; +using testing::Return; +using testing::ReturnRef; + +auto hash_from_str(std::string_view str) { + Hash256 hash; + std::copy_n(str.begin(), str.size(), hash.begin()); + return hash; +} + +auto hash_from_header(const BlockHeader &header) { + static crypto::HasherImpl hasher; + return hasher.blake2b_256(scale::encode(header).value()); +} + +class PolkadotTrieMock final : public trie::PolkadotTrie { + public: + PolkadotTrieMock(NodePtr root) : root{root} { + BOOST_ASSERT(this->root != nullptr); + } + + outcome::result contains(const face::View &key) const override { + throw std::runtime_error{"Not implemented"}; + } + + bool empty() const override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result> get( + const face::View &key) const override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result> tryGet( + const face::View &key) const override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result put(const BufferView &key, + BufferOrView &&value) override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result remove(const BufferView &key) override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result> clearPrefix( + const BufferView &prefix, + std::optional limit, + const OnDetachCallback &callback) override { + throw std::runtime_error{"Not implemented"}; + } + + NodePtr getRoot() override { + return root; + } + + ConstNodePtr getRoot() const override { + return root; + } + + outcome::result retrieveChild(const trie::BranchNode &parent, + uint8_t idx) const override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result retrieveChild(const trie::BranchNode &parent, + uint8_t idx) override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result retrieveValue( + trie::ValueAndHash &value) const override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result getNode( + ConstNodePtr parent, const trie::NibblesView &key_nibbles) override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result getNode( + ConstNodePtr parent, + const trie::NibblesView &key_nibbles) const override { + throw std::runtime_error{"Not implemented"}; + } + + outcome::result forNodeInPath( + ConstNodePtr parent, + const trie::NibblesView &path, + const std::function( + const trie::BranchNode &, uint8_t, const trie::TrieNode &)> &callback) + const override { + throw std::runtime_error{"Not implemented"}; + } + + std::unique_ptr trieCursor() const override { + auto cursor = + std::make_unique(); + EXPECT_CALL(*cursor, seekLowerBound(_)) + .WillRepeatedly(testing::Return(outcome::success())); + return cursor; + } + NodePtr root; +}; + +enum NodeType { NODE, DUMMY }; + +struct TrieNodeDesc { + NodeType type; + Hash256 merkle_value; + std::map children; +}; + +class TriePrunerTest : public testing::Test { + public: + void SetUp() { + testutil::prepareLoggers(soralog::Level::DEBUG); + auto config_mock = + std::make_shared(); + ON_CALL(*config_mock, statePruningDepth()).WillByDefault(Return(16)); + + trie_storage_mock.reset( + new testing::NiceMock()); + persistent_storage_mock.reset( + new testing::NiceMock); + serializer_mock.reset(new testing::NiceMock); + codec_mock.reset(new trie::CodecMock); + hasher = std::make_shared(); + + pruner_space = std::make_shared>(); + trie_pruner::TriePrunerImpl::TriePrunerInfo info{.last_pruned_block = {}}; + auto info_enc = scale::encode(info).value(); + static auto key = ":trie_pruner:info"_buf; + ON_CALL(*pruner_space, tryGetMock(key.view())) + .WillByDefault( + Return(outcome::success(std::make_optional(Buffer{info_enc})))); + ON_CALL(*pruner_space, put(key.view(), _)) + .WillByDefault(Return(outcome::success())); + + ON_CALL(*persistent_storage_mock, getSpace(kDefault)) + .WillByDefault(Invoke([this](auto) { return pruner_space; })); + + pruner.reset(new TriePrunerImpl( + std::make_shared(), + trie_storage_mock, + serializer_mock, + codec_mock, + persistent_storage_mock, + hasher, + config_mock)); + ASSERT_TRUE(pruner->prepare()); + } + + void initOnLastPrunedBlock(BlockInfo last_pruned, + const kagome::blockchain::BlockTree &block_tree) { + auto config_mock = + std::make_shared(); + ON_CALL(*config_mock, statePruningDepth()).WillByDefault(Return(16)); + trie_pruner::TriePrunerImpl::TriePrunerInfo info{.last_pruned_block = + last_pruned}; + + auto info_enc = scale::encode(info).value(); + static auto key = ":trie_pruner:info"_buf; + ON_CALL(*pruner_space, tryGetMock(key.view())) + .WillByDefault( + Return(outcome::success(std::make_optional(Buffer{info_enc})))); + + pruner.reset(new TriePrunerImpl( + std::make_shared(), + trie_storage_mock, + serializer_mock, + codec_mock, + persistent_storage_mock, + hasher, + config_mock)); + BOOST_ASSERT(pruner->prepare()); + ASSERT_OUTCOME_SUCCESS_TRY(recoverPrunerState(*pruner, block_tree)); + } + + auto makeTrie(TrieNodeDesc desc) const { + auto trie = std::make_shared( + std::static_pointer_cast(makeNode(desc))); + + return trie; + } + + std::shared_ptr makeNode(TrieNodeDesc desc) const { + if (desc.type == NODE) { + if (desc.children.empty()) { + auto node = std::make_shared( + trie::KeyNibbles{}, + trie::ValueAndHash{Buffer{desc.merkle_value}, {}}); + return node; + } + auto node = std::make_shared(trie::KeyNibbles{}, + Buffer{desc.merkle_value}); + for (auto [idx, child] : desc.children) { + node->children[idx] = makeNode(child); + } + return node; + } + if (desc.type == DUMMY) { + return std::make_shared(desc.merkle_value); + } + return nullptr; + } + + std::shared_ptr makeTransparentNode(TrieNodeDesc desc) const { + BOOST_ASSERT(desc.type != DUMMY); + return std::static_pointer_cast(makeNode(desc)); + } + + std::unique_ptr pruner; + std::shared_ptr serializer_mock; + std::shared_ptr trie_storage_mock; + std::shared_ptr> persistent_storage_mock; + std::shared_ptr codec_mock; + std::shared_ptr hasher; + std::shared_ptr> pruner_space; +}; + +struct NodeRetriever { + template + outcome::result operator()( + const std::shared_ptr &node, const F &) { + if (auto dummy = std::dynamic_pointer_cast(node); + dummy != nullptr) { + auto decoded = decoded_nodes.at(*dummy->db_key.asHash()); + return decoded; + } + if (auto trie_node = std::dynamic_pointer_cast(node); + trie_node != nullptr) { + return trie_node; + } + return nullptr; + } + + std::map> decoded_nodes; +}; + +auto setCodecExpectations(trie::CodecMock &mock, trie::Codec &codec) { + EXPECT_CALL(mock, encodeNode(_, _, _)) + .WillRepeatedly(Invoke([&codec](auto &node, auto ver, auto &visitor) { + return codec.encodeNode(node, ver, visitor); + })); + EXPECT_CALL(mock, decodeNode(_)).WillRepeatedly(Invoke([&codec](auto n) { + return codec.decodeNode(n); + })); + EXPECT_CALL(mock, merkleValue(_)).WillRepeatedly(Invoke([&codec](auto &v) { + return codec.merkleValue(v); + })); + EXPECT_CALL(mock, merkleValue(_, _, _)) + .WillRepeatedly(Invoke([&codec](auto &node, auto ver, auto) { + return codec.merkleValue(node, ver); + })); + EXPECT_CALL(mock, hash256(_)).WillRepeatedly(Invoke([&codec](auto &v) { + return codec.hash256(v); + })); + EXPECT_CALL(mock, shouldBeHashed(_, _)) + .WillRepeatedly(Invoke([&codec](auto &value, auto version) { + return codec.shouldBeHashed(value, version); + })); +} + +TEST_F(TriePrunerTest, BasicScenario) { + auto codec = std::make_shared(); + + ON_CALL(*codec_mock, merkleValue(_, _, _)) + .WillByDefault(Invoke([](auto &node, auto version, auto) { + return trie::MerkleValue::create( + *static_cast(node).getValue().value) + .value(); + })); + + auto trie = makeTrie( + {NODE, + "root1"_hash256, + {{0, {NODE, "_0"_hash256, {}}}, {5, {NODE, "_5"_hash256, {}}}}}); + ON_CALL(*serializer_mock, + retrieveNode(A &>(), _)) + .WillByDefault( + Invoke([](const std::shared_ptr &node, auto) { + return std::dynamic_pointer_cast(node); + })); + ASSERT_OUTCOME_SUCCESS_TRY( + pruner->addNewState(*trie, trie::StateVersion::V1)); + ASSERT_EQ(pruner->getTrackedNodesNum(), 3); + + auto trie_1 = makeTrie( + {NODE, + "root2"_hash256, + {{0, {NODE, "_0"_hash256, {}}}, {5, {NODE, "_5"_hash256, {}}}}}); + ASSERT_OUTCOME_SUCCESS_TRY( + pruner->addNewState(*trie_1, trie::StateVersion::V1)); + EXPECT_EQ(pruner->getTrackedNodesNum(), 4); + + EXPECT_CALL( + *serializer_mock, + retrieveNode(testing::A &>(), + _)) + .WillRepeatedly(testing::Invoke(NodeRetriever{ + {{"_0"_hash256, makeTransparentNode({NODE, "_0"_hash256, {}})}, + {"_5"_hash256, makeTransparentNode({NODE, "_5"_hash256, {}})}}})); + + EXPECT_CALL(*trie_storage_mock, batch()).WillRepeatedly(Invoke([]() { + auto batch = std::make_unique>(); + EXPECT_CALL(*batch, remove(_)).WillRepeatedly(Return(outcome::success())); + EXPECT_CALL(*batch, commit()).WillOnce(Return(outcome::success())); + return batch; + })); + EXPECT_CALL(*serializer_mock, retrieveTrie("root1"_hash256, _)) + .WillOnce(testing::Return(trie)); + ASSERT_OUTCOME_SUCCESS_TRY(pruner->pruneFinalized( + BlockHeader{.number = 1, .state_root = "root1"_hash256})); + ASSERT_EQ(pruner->getTrackedNodesNum(), 3); + + EXPECT_CALL(*serializer_mock, retrieveTrie("root2"_hash256, _)) + .WillOnce(testing::Return(trie_1)); + ASSERT_OUTCOME_SUCCESS_TRY(pruner->pruneFinalized( + BlockHeader{.number = 2, .state_root = "root2"_hash256})); + ASSERT_EQ(pruner->getTrackedNodesNum(), 0); +} + +template +Buffer randomBuffer(RandomDevice &rand) { + Buffer buf; + int size = rand() % 8; + for (int i = 0; i < size; i++) { + buf.putUint8(rand()); + } + return buf; +} + +template +void forAllLoadedNodes(const trie::TrieNode &node, const F &f) { + f(node); + if (node.isBranch()) { + auto &branch = static_cast(node); + for (auto &child : branch.children) { + if (auto transparent_child = + dynamic_cast(child.get()); + transparent_child != nullptr) { + forAllLoadedNodes(*transparent_child, f); + } + } + } +} + +template +void forAllNodes(trie::PolkadotTrie &trie, trie::TrieNode &root, const F &f) { + f(root); + if (root.isBranch()) { + auto &branch = static_cast(root); + uint8_t idx = 0; + for (auto &child : branch.children) { + if (child != nullptr) { + auto loaded_child = trie.retrieveChild(branch, idx).value(); + forAllNodes(trie, *loaded_child, f); + } + idx++; + } + } +} + +std::set collectReferencedNodes(trie::PolkadotTrie &trie, + const trie::PolkadotCodec &codec) { + std::set res; + if (trie.getRoot() == nullptr) { + return {}; + } + forAllNodes(trie, *trie.getRoot(), [&res, &codec](auto &node) { + auto enc = codec.encodeNode(node, trie::StateVersion::V1, nullptr).value(); + res.insert(*codec.merkleValue(enc).asHash()); + }); + return res; +} + +void generateRandomTrie(size_t inserts, + trie::PolkadotTrie &trie, + std::set &inserted_keys) { + std::mt19937 rand; + rand.seed(42); + + for (unsigned j = 0; j < inserts; j++) { + auto k = randomBuffer(rand); + inserted_keys.insert(k); + ASSERT_OUTCOME_SUCCESS_TRY(trie.put(k, randomBuffer(rand))); + } +} + +template +void makeRandomTrieChanges(size_t inserts, + size_t removes, + trie::PolkadotTrie &trie, + std::set &inserted_keys, + RandomDevice &rand) { + for (unsigned j = 0; j < inserts; j++) { + auto k = randomBuffer(rand); + inserted_keys.insert(k); + ASSERT_OUTCOME_SUCCESS_TRY(trie.put(k, randomBuffer(rand))); + } + for (unsigned j = 0; j < removes; j++) { + auto it = inserted_keys.begin(); + std::advance(it, rand() % inserted_keys.size()); + auto &k = *it; + ASSERT_OUTCOME_SUCCESS_TRY(trie.remove(k)); + inserted_keys.erase(k); + } +} + +TEST_F(TriePrunerTest, RandomTree) { + constexpr unsigned STATES_NUM = 30; + constexpr unsigned INSERT_PER_STATE = 100; + constexpr unsigned REMOVES_PER_STATE = 25; + + auto trie = trie::PolkadotTrieImpl::createEmpty(); + auto codec = std::make_shared(); + setCodecExpectations(*codec_mock, *codec); + auto trie_factory = std::make_shared(); + + std::map node_storage; + std::set inserted_keys; + + EXPECT_CALL(*trie_storage_mock, get(_)) + .WillRepeatedly( + Invoke([&node_storage](auto &k) -> outcome::result { + auto it = node_storage.find(k); + if (it == node_storage.end()) { + return DatabaseError::NOT_FOUND; + } + return BufferOrView{it->second.view()}; + })); + + trie::TrieSerializerImpl serializer{trie_factory, codec, trie_storage_mock}; + std::vector> kv; + std::mt19937 rand; + rand.seed(42); + + std::vector roots; + + std::set total_set; + + EXPECT_CALL(*serializer_mock, retrieveTrie(_, _)) + .WillRepeatedly(Invoke([&serializer](auto root, const auto &) { + return serializer.retrieveTrie(root, nullptr); + })); + EXPECT_CALL( + *serializer_mock, + retrieveNode(A &>(), _)) + .WillRepeatedly(Invoke([&serializer](auto &node, auto &) { + return serializer.retrieveNode(node, nullptr); + })); + + for (unsigned i = 0; i < STATES_NUM; i++) { + EXPECT_CALL(*trie_storage_mock, batch()).WillOnce(Invoke([&node_storage]() { + auto batch_mock = + std::make_unique>(); + EXPECT_CALL(*batch_mock, put(_, _)) + .WillRepeatedly(Invoke([&node_storage](auto &k, auto &v) { + node_storage[k] = v; + return outcome::success(); + })); + EXPECT_CALL(*batch_mock, commit()) + .WillRepeatedly(Return(outcome::success())); + return batch_mock; + })); + + for (unsigned j = 0; j < INSERT_PER_STATE; j++) { + auto k = randomBuffer(rand); + inserted_keys.insert(k); + ASSERT_OUTCOME_SUCCESS_TRY(trie->put(k, randomBuffer(rand))); + } + for (unsigned j = 0; j < REMOVES_PER_STATE; j++) { + auto it = inserted_keys.begin(); + std::advance(it, rand() % inserted_keys.size()); + auto &k = *it; + ASSERT_OUTCOME_SUCCESS_TRY(trie->remove(k)); + inserted_keys.erase(k); + } + ASSERT_OUTCOME_SUCCESS_TRY( + trie->clearPrefix(Buffer{{static_cast(rand() % 256)}}, + std::nullopt, + [](auto &, auto) -> outcome::result { + return outcome::success(); + })); + auto new_set = collectReferencedNodes(*trie, *codec); + total_set.merge(new_set); + ASSERT_OUTCOME_SUCCESS_TRY( + pruner->addNewState(*trie, trie::StateVersion::V0)); + std::set tracked_set; + pruner->forRefCounts( + [&](auto &node, auto count) { tracked_set.insert(node); }); + std::set diff; + std::set_symmetric_difference(total_set.begin(), + total_set.end(), + tracked_set.begin(), + tracked_set.end(), + std::inserter(diff, diff.begin())); + ASSERT_OUTCOME_SUCCESS(root, + serializer.storeTrie(*trie, trie::StateVersion::V0)); + roots.push_back(root); + + if (i >= 16) { + EXPECT_CALL(*trie_storage_mock, batch()) + .WillOnce(Invoke([&node_storage]() { + auto batch = + std::make_unique>(); + EXPECT_CALL(*batch, remove(_)) + .WillRepeatedly(Invoke([&node_storage](auto &k) { + node_storage.erase(k); + return outcome::success(); + })); + EXPECT_CALL(*batch, commit()).WillOnce(Return(outcome::success())); + return batch; + })); + + auto &root = roots[i - 16]; + + ASSERT_OUTCOME_SUCCESS_TRY(pruner->pruneFinalized( + BlockHeader{.number = i - 16, .state_root = root})); + } + } + for (unsigned i = STATES_NUM - 16; i < STATES_NUM; i++) { + EXPECT_CALL(*trie_storage_mock, batch()).WillOnce(Invoke([&node_storage]() { + auto batch = std::make_unique>(); + EXPECT_CALL(*batch, remove(_)) + .WillRepeatedly(Invoke([&node_storage](auto &k) { + node_storage.erase(k); + return outcome::success(); + })); + EXPECT_CALL(*batch, commit()).WillOnce(Return(outcome::success())); + return batch; + })); + + auto &root = roots[i]; + ASSERT_OUTCOME_SUCCESS_TRY( + pruner->pruneFinalized(BlockHeader{.number = i, .state_root = root})); + } + for (auto &[hash, node] : node_storage) { + std::cout << hash << "\n"; + } + + ASSERT_EQ(node_storage.size(), 0); +} + +TEST_F(TriePrunerTest, RestoreStateFromGenesis) { + auto block_tree = std::make_shared(); + auto genesis_hash = "genesis"_hash256; + ON_CALL(*block_tree, getGenesisBlockHash()) + .WillByDefault(ReturnRef(genesis_hash)); + + std::map headers; + std::map hash_to_number; + for (BlockNumber n = 1; n <= 6; n++) { + auto parent_hash = headers.count(n - 1) + ? hash_from_header(headers.at(n - 1)) + : "genesis"_hash256; + headers[n] = BlockHeader{ + .parent_hash = parent_hash, + .number = n, + .state_root = hash_from_str("root_hash" + std::to_string(n)), + }; + hash_to_number[hash_from_header(headers.at(n))] = n; + } + + ON_CALL(*block_tree, getBlockHash(_)) + .WillByDefault(Invoke([&headers](auto number) { + return hash_from_header(headers.at(number)); + })); + + ON_CALL(*block_tree, getBlockHeader(_)).WillByDefault(Invoke([&](auto &hash) { + if (hash == "genesis"_hash256) { + return BlockHeader{.number = 0, .state_root = "genesis_root"_hash256}; + } + return headers.at(hash_to_number.at(hash)); + })); + + ON_CALL(*block_tree, getChildren(_)) + .WillByDefault(Return(std::vector{})); + + auto mock_block = [&](unsigned int number) { + auto str_number = std::to_string(number); + + primitives::BlockHeader header = headers.at(number); + auto root_hash = header.state_root; + auto hash = hash_from_header(header); + ON_CALL(*block_tree, getChildren(header.parent_hash)) + .WillByDefault(Return(std::vector{hash})); + + auto trie = trie::PolkadotTrieImpl::createEmpty(); + trie->put(Buffer::fromString("key" + str_number), + Buffer::fromString("val" + str_number)) + .value(); + EXPECT_CALL(*serializer_mock, retrieveTrie(root_hash, _)) + .WillOnce(Return(trie)); + EXPECT_CALL(*codec_mock, merkleValue(testing::Ref(*trie->getRoot()), _, _)) + .WillRepeatedly(Return( + trie::MerkleValue(hash_from_str("merkle_val" + str_number)))); + auto enc = Buffer::fromString("encoded_node" + str_number); + ON_CALL(*codec_mock, encodeNode(testing::Ref(*trie->getRoot()), _, _)) + .WillByDefault(Return(enc)); + ON_CALL(*codec_mock, hash256(testing::ElementsAreArray(enc))) + .WillByDefault(Return(root_hash)); + }; + mock_block(4); + mock_block(5); + mock_block(6); + + trie_pruner::TriePrunerImpl::TriePrunerInfo info{ + .last_pruned_block = BlockInfo{3, hash_from_header(headers.at(3))}}; + auto info_enc = scale::encode(info).value(); + static auto key = ":trie_pruner:info"_buf; + ON_CALL(*pruner_space, tryGetMock(key.view())) + .WillByDefault( + Return(outcome::success(std::make_optional(Buffer{info_enc})))); + + ON_CALL(*block_tree, getLastFinalized()) + .WillByDefault(Return(BlockInfo{3, hash_from_header(headers.at(3))})); + + initOnLastPrunedBlock(BlockInfo{3, hash_from_header(headers.at(3))}, + *block_tree); + + ASSERT_EQ(pruner->getTrackedNodesNum(), 3); +} + +std::shared_ptr clone(const trie::PolkadotTrie &trie) { + auto new_trie = trie::PolkadotTrieImpl::createEmpty(); + auto cursor = trie.trieCursor(); + EXPECT_OUTCOME_TRUE_1(cursor->next()); + while (cursor->isValid()) { + EXPECT_OUTCOME_TRUE_1( + new_trie->put(cursor->key().value(), cursor->value().value())); + EXPECT_OUTCOME_TRUE_1(cursor->next()); + } + return new_trie; +} + +TEST_F(TriePrunerTest, FastSyncScenario) { + std::unordered_map node_storage; + constexpr size_t LAST_BLOCK_NUMBER = 100; + + auto block_tree = + std::make_shared>(); + + ON_CALL(*trie_storage_mock, get(_)) + .WillByDefault(Invoke( + [&](auto &key) -> outcome::result { + if (node_storage.count(key) == 0) { + return DatabaseError::NOT_FOUND; + } + return kagome::common::BufferOrView{node_storage.at(key).view()}; + })); + + ON_CALL(*trie_storage_mock, batch()).WillByDefault(Invoke([&]() { + auto batch = std::make_unique< + testing::NiceMock>>(); + ON_CALL(*batch, put(_, _)) + .WillByDefault(Invoke([&](auto &key, auto &value) { + node_storage[key] = value; + return outcome::success(); + })); + ON_CALL(*batch, remove(_)).WillByDefault(Invoke([&](auto &key) { + node_storage.erase(key); + return outcome::success(); + })); + ON_CALL(*batch, commit()).WillByDefault(Return(outcome::success())); + return batch; + })); + + auto genesis_trie = trie::PolkadotTrieImpl::createEmpty(); + std::set inserted_keys; + generateRandomTrie(100, *genesis_trie, inserted_keys); + + auto codec = std::make_shared(); + setCodecExpectations(*codec_mock, *codec); + + auto trie_factory = std::make_shared(); + auto genesis_state_root = codec->hash256( + codec->encodeNode(*genesis_trie->getRoot(), trie::StateVersion::V0) + .value()); + + trie::TrieSerializerImpl serializer{trie_factory, codec, trie_storage_mock}; + + ON_CALL(*serializer_mock, retrieveTrie(genesis_state_root, _)) + .WillByDefault(Return(genesis_trie)); + + ON_CALL(*serializer_mock, + retrieveNode(A &>(), _)) + .WillByDefault(Invoke([&serializer](auto &node, auto &cb) { + return serializer.retrieveNode(node, cb); + })); + ON_CALL(*serializer_mock, storeTrie(_, _)) + .WillByDefault(Invoke([&serializer](auto &trie, auto version) { + return serializer.storeTrie(trie, version); + })); + + ASSERT_OUTCOME_SUCCESS_TRY( + serializer_mock->storeTrie(*genesis_trie, trie::StateVersion::V0)); + + BlockHeader genesis_header{ + .parent_hash = ""_hash256, + .number = 0, + .state_root = genesis_state_root, + }; + ON_CALL(*block_tree, getBlockHeader(hash_from_header(genesis_header))) + .WillByDefault(Return(genesis_header)); + + ON_CALL(*block_tree, getGenesisBlockHash()) + .WillByDefault( + testing::ReturnRefOfCopy(hash_from_header(genesis_header))); + + ON_CALL(*block_tree, getLastFinalized()) + .WillByDefault(Return(BlockInfo{0, hash_from_header(genesis_header)})); + + std::vector headers{genesis_header}; + headers.reserve(LAST_BLOCK_NUMBER); + std::vector hashes{hash_from_header(genesis_header)}; + headers.reserve(LAST_BLOCK_NUMBER); + std::vector> tries{genesis_trie}; + std::mt19937 rand; + rand.seed(42); + + auto mock_header_only = [&](BlockNumber n) { + std::shared_ptr block_trie{clone(*tries[n - 1])}; + tries.push_back(block_trie); + makeRandomTrieChanges(30, 10, *block_trie, inserted_keys, rand); + + auto block_state_root = codec->hash256( + codec->encodeNode(*block_trie->getRoot(), trie::StateVersion::V0) + .value()); + + BlockHeader block_header{ + .parent_hash = hashes[n - 1], + .number = n, + .state_root = block_state_root, + }; + auto hash = hash_from_header(block_header); + headers.push_back(block_header); + hashes.push_back(hash); + ON_CALL(*block_tree, getBlockHash(n)).WillByDefault(Return(hash)); + ON_CALL(*block_tree, getBlockHeader(hash)) + .WillByDefault(Return(block_header)); + ON_CALL(*block_tree, getChildren(hashes[n - 1])) + .WillByDefault(Return(std::vector{hash})); + ON_CALL(*block_tree, getChildren(hashes[n])) + .WillByDefault(Return(std::vector{})); + }; + + EXPECT_CALL(*serializer_mock, retrieveTrie(genesis_state_root, _)) + .WillRepeatedly(Return(genesis_trie)); + + auto mock_full_block = [&](BlockNumber n) { + mock_header_only(n); + ASSERT_OUTCOME_SUCCESS_TRY( + serializer_mock->storeTrie(*tries[n], trie::StateVersion::V0)); + EXPECT_CALL(*serializer_mock, retrieveTrie(headers[n].state_root, _)) + .WillRepeatedly(Return(tries[n])); + }; + + for (BlockNumber n = 1; n < 30; n++) { + mock_full_block(n); + } + for (BlockNumber n = 30; n < 80; n++) { + mock_header_only(n); + EXPECT_CALL(*serializer_mock, retrieveTrie(headers[n].state_root, _)) + .WillRepeatedly(Return(DatabaseError::NOT_FOUND)); + } + + ASSERT_OUTCOME_SUCCESS_TRY(recoverPrunerState(*pruner, *block_tree)); + + for (BlockNumber n = 80; n < LAST_BLOCK_NUMBER; n++) { + mock_full_block(n); + ASSERT_OUTCOME_SUCCESS_TRY( + pruner->addNewState(*tries[n], trie::StateVersion::V0)); + } + ASSERT_NE(node_storage.size(), 0); + for (BlockNumber n = 0; n < LAST_BLOCK_NUMBER; n++) { + EXPECT_CALL(*serializer_mock, retrieveTrie(headers[n].state_root, _)) + .WillOnce(Return(tries[n])); + if (auto trie_res = + serializer.retrieveTrie(headers[n].state_root, [](auto) {}); + trie_res.has_value()) { + auto &trie = trie_res.value(); + auto cursor = trie->cursor(); + ASSERT_OUTCOME_SUCCESS_TRY(cursor->next()); + while (cursor->isValid()) { + ASSERT_TRUE(cursor->value().has_value()); + ASSERT_OUTCOME_SUCCESS_TRY(cursor->next()); + } + } + + ASSERT_OUTCOME_SUCCESS_TRY(pruner->pruneFinalized(headers[n])); + } +} diff --git a/test/external-project-test/link_libraries.cmake b/test/external-project-test/link_libraries.cmake index a7939270d4..773b6d7849 100644 --- a/test/external-project-test/link_libraries.cmake +++ b/test/external-project-test/link_libraries.cmake @@ -1,6 +1,7 @@ function(external_project_link_libraries target prefix) set(targets + app_config executor runtime_wavm core_api diff --git a/test/external-project-test/src/main.cpp b/test/external-project-test/src/main.cpp index 8b100fdd08..c4ae79abc4 100644 --- a/test/external-project-test/src/main.cpp +++ b/test/external-project-test/src/main.cpp @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include +#include #include #include #include @@ -37,20 +39,21 @@ #include #include #include +#include #include #include kagome::storage::trie::RootHash trieRoot( const std::vector> &key_vals) { - auto trie = kagome::storage::trie::PolkadotTrieImpl(); + auto trie = kagome::storage::trie::PolkadotTrieImpl::createEmpty(); auto codec = kagome::storage::trie::PolkadotCodec(); for (const auto &[key, val] : key_vals) { - [[maybe_unused]] auto res = trie.put(key, val.view()); + [[maybe_unused]] auto res = trie->put(key, val.view()); BOOST_ASSERT_MSG(res.has_value(), "Insertion into trie failed"); } - auto root = trie.getRoot(); + auto root = trie->getRoot(); if (root == nullptr) { return codec.hash256(kagome::common::BufferView{{0}}); } @@ -93,6 +96,9 @@ int main() { auto code_substitutes = chain_spec->codeSubstitutes(); auto storage = std::make_shared(); + auto config = std::make_shared( + kagome::log::createLogger("AppConfiguration")); + auto trie_factory = std::make_shared(); auto codec = std::make_shared(); @@ -101,9 +107,22 @@ int main() { auto serializer = std::make_shared( trie_factory, codec, storage_backend); + auto app_state_manager = + std::make_shared(); + + auto state_pruner = + std::make_shared( + app_state_manager, + storage_backend, + serializer, + codec, + database, + hasher, + config); + std::shared_ptr trie_storage = kagome::storage::trie::TrieStorageImpl::createEmpty( - trie_factory, codec, serializer) + trie_factory, codec, serializer, state_pruner) .value(); auto batch = diff --git a/test/mock/core/application/app_configuration_mock.hpp b/test/mock/core/application/app_configuration_mock.hpp index a05f168817..30ab8c5a61 100644 --- a/test/mock/core/application/app_configuration_mock.hpp +++ b/test/mock/core/application/app_configuration_mock.hpp @@ -144,6 +144,11 @@ namespace kagome::application { MOCK_METHOD(bool, isTelemetryEnabled, (), (const, override)); + MOCK_METHOD(std::optional, + statePruningDepth, + (), + (const, override)); + MOCK_METHOD(StorageBackend, storageBackend, (), (const, override)); MOCK_METHOD(uint32_t, dbCacheSize, (), (const, override)); diff --git a/test/mock/core/blockchain/block_tree_mock.hpp b/test/mock/core/blockchain/block_tree_mock.hpp index 27724117e3..234810b68c 100644 --- a/test/mock/core/blockchain/block_tree_mock.hpp +++ b/test/mock/core/blockchain/block_tree_mock.hpp @@ -17,7 +17,7 @@ namespace kagome::blockchain { (), (const, override)); - MOCK_METHOD(outcome::result, + MOCK_METHOD(outcome::result>, getBlockHash, (primitives::BlockNumber), (const, override)); diff --git a/test/mock/core/storage/trie/serialization/codec_mock.hpp b/test/mock/core/storage/trie/serialization/codec_mock.hpp new file mode 100644 index 0000000000..6d6daf8327 --- /dev/null +++ b/test/mock/core/storage/trie/serialization/codec_mock.hpp @@ -0,0 +1,48 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_CODEC_MOCK_HPP +#define KAGOME_CODEC_MOCK_HPP + +#include + +#include "storage/trie/serialization/codec.hpp" + +namespace kagome::storage::trie { + + class CodecMock : public trie::Codec { + public: + MOCK_METHOD(outcome::result, + encodeNode, + (const trie::TrieNode &opaque_node, + trie::StateVersion version, + const ChildVisitor &child_visitor), + (const, override)); + MOCK_METHOD(outcome::result>, + decodeNode, + (common::BufferView encoded_data), + (const, override)); + MOCK_METHOD(MerkleValue, + merkleValue, + (const BufferView &buf), + (const, override)); + MOCK_METHOD(outcome::result, + merkleValue, + (const trie::OpaqueTrieNode &opaque_node, + trie::StateVersion version, + const ChildVisitor &child_visitor), + (const, override)); + MOCK_METHOD(common::Hash256, + hash256, + (const BufferView &buf), + (const, override)); + MOCK_METHOD(bool, + shouldBeHashed, + (const trie::ValueAndHash &value, + trie::StateVersion version_opt), + (const, override)); + }; +} // namespace kagome::storage::trie +#endif // KAGOME_CODEC_MOCK_HPP diff --git a/test/mock/core/storage/trie/serialization/trie_serializer_mock.hpp b/test/mock/core/storage/trie/serialization/trie_serializer_mock.hpp index be40157bda..153bfacef0 100644 --- a/test/mock/core/storage/trie/serialization/trie_serializer_mock.hpp +++ b/test/mock/core/storage/trie/serialization/trie_serializer_mock.hpp @@ -23,8 +23,25 @@ namespace kagome::storage::trie { MOCK_METHOD(outcome::result>, retrieveTrie, - (const common::Buffer &, OnNodeLoaded), + (RootHash, OnNodeLoaded), (const, override)); + + MOCK_METHOD(outcome::result, + retrieveNode, + (MerkleValue db_key, const OnNodeLoaded &on_node_loaded), + (const, override)); + + MOCK_METHOD(outcome::result, + retrieveNode, + (const std::shared_ptr &node, + const OnNodeLoaded &on_node_loaded), + (const, override)); + + MOCK_METHOD(outcome::result>, + retrieveValue, + (common::Hash256 const &hash, + const OnNodeLoaded &on_node_loaded), + (const)); }; } // namespace kagome::storage::trie diff --git a/test/mock/core/storage/trie/trie_storage_backend_mock.hpp b/test/mock/core/storage/trie/trie_storage_backend_mock.hpp index a815f82745..ec037c70d7 100644 --- a/test/mock/core/storage/trie/trie_storage_backend_mock.hpp +++ b/test/mock/core/storage/trie/trie_storage_backend_mock.hpp @@ -16,31 +16,37 @@ namespace kagome::storage::trie { public: MOCK_METHOD(std::unique_ptr, batch, (), (override)); - MOCK_METHOD(std::unique_ptr>, + MOCK_METHOD((std::unique_ptr>), cursor, (), (override)); - MOCK_METHOD(outcome::result, + MOCK_METHOD(outcome::result, get, - (const Buffer &key), + (const BufferView &key), + (const, override)); + + MOCK_METHOD(outcome::result>, + tryGet, + (const BufferView &key), (const, override)); MOCK_METHOD(outcome::result, contains, - (const Buffer &key), + (const BufferView &key), (const, override)); + MOCK_METHOD(bool, empty, (), (const, override)); + MOCK_METHOD(outcome::result, put, - (const Buffer &key, const Buffer &value), + (const BufferView &key, BufferOrView &&value), (override)); - outcome::result put(const common::Buffer &k, - common::Buffer &&v) override { - return put(k, v); - } - MOCK_METHOD(outcome::result, remove, (const Buffer &key), (override)); + MOCK_METHOD(outcome::result, + remove, + (const BufferView &key), + (override)); }; } // namespace kagome::storage::trie diff --git a/test/mock/core/storage/trie_pruner/trie_pruner_mock.hpp b/test/mock/core/storage/trie_pruner/trie_pruner_mock.hpp new file mode 100644 index 0000000000..9917982e8b --- /dev/null +++ b/test/mock/core/storage/trie_pruner/trie_pruner_mock.hpp @@ -0,0 +1,58 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_TRIE_PRUNER_MOCK_HPP +#define KAGOME_TRIE_PRUNER_MOCK_HPP + +#include "storage/trie_pruner/trie_pruner.hpp" + +#include "gmock/gmock.h" + +namespace kagome::storage::trie_pruner { + + class TriePrunerMock final : public TriePruner { + public: + MOCK_METHOD(outcome::result, + addNewState, + (const storage::trie::RootHash &new_trie, + trie::StateVersion version), + (override)); + + MOCK_METHOD(outcome::result, + addNewState, + (const trie::PolkadotTrie &new_trie, + trie::StateVersion version), + (override)); + + MOCK_METHOD(outcome::result, + pruneFinalized, + (const primitives::BlockHeader &state), + (override)); + + MOCK_METHOD(outcome::result, + pruneDiscarded, + (const primitives::BlockHeader &state), + (override)); + + MOCK_METHOD(outcome::result, + restoreState, + (const primitives::BlockHeader &base_block, + const blockchain::BlockTree &block_tree), + (override)); + + MOCK_METHOD(std::optional, + getLastPrunedBlock, + (), + (const, override)); + + MOCK_METHOD(std::optional, + getPruningDepth, + (), + (const, override)); + }; + +} // namespace kagome::storage::trie_pruner + +#endif // KAGOME_TRIE_PRUNER_MOCK_HPP diff --git a/test/testutil/storage/polkadot_trie_printer.hpp b/test/testutil/storage/polkadot_trie_printer.hpp index b728588c6b..f96f685e01 100644 --- a/test/testutil/storage/polkadot_trie_printer.hpp +++ b/test/testutil/storage/polkadot_trie_printer.hpp @@ -41,8 +41,8 @@ namespace kagome::storage::trie { } else { stream_ << std::setfill('-') << std::setw(nest_level) << "" << std::setw(0) << "(leaf) key: <" - << hex_lower(node->key_nibbles.toByteBuffer()) - << "> value: " << node->value.value->toHex() << "\n"; + << hex_lower(node->getKeyNibbles().toByteBuffer()) + << "> value: " << node->getValue().value->toHex() << "\n"; } return stream_; } @@ -53,11 +53,12 @@ namespace kagome::storage::trie { size_t nest_level) { std::string indent(nest_level, '\t'); auto value = - (node->value ? "\"" + node->value.value->toHex() + "\"" : "NONE"); + (node->getValue() ? "\"" + node->getValue().value->toHex() + "\"" + : "NONE"); auto branch = std::dynamic_pointer_cast(node); stream_ << std::setfill('-') << std::setw(nest_level) << "" << std::setw(0) << "(branch) key: <" - << hex_lower(node->key_nibbles.toByteBuffer()) + << hex_lower(node->getKeyNibbles().toByteBuffer()) << "> value: " << value << " children: "; for (size_t i = 0; i < branch->children.size(); i++) { if (branch->children[i]) { @@ -65,9 +66,7 @@ namespace kagome::storage::trie { } } stream_ << "\n"; - if (false) { - printEncAndHash(node, nest_level); - } + printEncAndHash(node, nest_level); for (size_t i = 0; i < branch->children.size(); i++) { auto child = branch->children.at(i); if (child) { @@ -91,16 +90,16 @@ namespace kagome::storage::trie { } if (print_hash_) { stream_ << std::setfill('-') << std::setw(nest_level) << "" - << std::setw(0) - << "hash: " << common::hex_upper(codec_.merkleValue(enc)) + << std::setw(0) << "hash: " + << common::hex_upper(codec_.merkleValue(enc).asBuffer()) << "\n"; } } Stream &stream_; PolkadotCodec codec_; - bool print_enc_; - bool print_hash_; + bool print_enc_ = false; + bool print_hash_ = true; }; } // namespace printer_internal diff --git a/zombienet/README.md b/zombienet/README.md index fd11df4f21..a0a020d544 100644 --- a/zombienet/README.md +++ b/zombienet/README.md @@ -33,10 +33,10 @@ Using kubernetes as provider we can simply spawn this network by running: ```sh zombienet test -p kubernetes 0001-parachains-smoke-test.zndsl ``` -or simplier, since kubernetes is the default provider as: +or simpler, since kubernetes is the default provider as: ```sh zombienet test 0001-parachains-smoke-test.zndsl ``` -NOTE: Add correct images for tests in confing(toml file) \ No newline at end of file +NOTE: Add correct images for tests in config(toml file) \ No newline at end of file