From 62b9a92b02f3586df7c034df9e1782eacff92432 Mon Sep 17 00:00:00 2001 From: Harrm Date: Fri, 24 Nov 2023 11:35:00 +0300 Subject: [PATCH] Parachain instance cache warmup (#1851) * Added precompilation of parachain modules -- at node startup, parachain validation codes are pulled from the state of the last finalized block and compiled into wasm modules, which are stored in the RuntimeInstancesPool, a cache used by pvf executor. * Add test for multithreaded module compilation * Disable parachain precompilation for non-validating nodes * Make pools_mtx non-shared * build fix Signed-off-by: iceseer * Fix errors from master --------- Signed-off-by: iceseer Co-authored-by: iceseer Co-authored-by: Alexander Lednev <57529355+iceseer@users.noreply.github.com> --- cmake/clang-format.cmake | 3 + cmake/toolchain/compiler/gcc-13.cmake | 34 ++++ cmake/toolchain/gcc-13_cxx20.cmake | 2 + core/application/app_configuration.hpp | 2 + core/application/app_state_manager.hpp | 51 +++-- .../impl/app_configuration_impl.cpp | 15 ++ .../impl/app_configuration_impl.hpp | 11 + core/blockchain/impl/block_tree_impl.cpp | 4 +- core/blockchain/indexer.hpp | 2 +- core/common/empty.hpp | 6 +- core/common/monadic_utils.hpp | 9 +- core/common/visitor.hpp | 2 +- core/consensus/CMakeLists.txt | 1 - core/consensus/babe/CMakeLists.txt | 1 + .../babe/impl/babe_config_repository_impl.cpp | 2 +- core/consensus/grandpa/CMakeLists.txt | 16 +- .../grandpa/impl/authority_manager_impl.cpp | 2 +- core/consensus/timeline/CMakeLists.txt | 3 + core/host_api/impl/storage_extension.cpp | 4 +- core/injector/application_injector.cpp | 15 +- core/log/configurator.cpp | 2 + core/network/CMakeLists.txt | 1 + core/network/beefy/beefy.cpp | 4 +- .../helpers/scale_message_read_writer.hpp | 2 +- core/network/protobuf/CMakeLists.txt | 2 + core/network/types/collator_messages.hpp | 4 +- core/parachain/CMakeLists.txt | 1 + .../availability/bitfield/signer.cpp | 2 +- .../availability/bitfield/store_impl.cpp | 2 +- core/parachain/availability/chunks.hpp | 2 +- core/parachain/pvf/module_precompiler.cpp | 189 ++++++++++++++++++ core/parachain/pvf/module_precompiler.hpp | 59 ++++++ core/parachain/pvf/pvf_impl.cpp | 47 ++++- core/parachain/pvf/pvf_impl.hpp | 32 ++- core/parachain/types.hpp | 2 +- .../validator/impl/parachain_processor.cpp | 2 +- .../binaryen/core_api_factory_impl.cpp | 8 +- .../binaryen/module/module_factory_impl.cpp | 2 +- .../binaryen/module/module_factory_impl.hpp | 2 +- core/runtime/binaryen/module/module_impl.cpp | 16 +- core/runtime/binaryen/module/module_impl.hpp | 7 +- .../binaryen/module/module_instance_impl.cpp | 4 +- core/runtime/common/CMakeLists.txt | 5 +- .../runtime/common/module_repository_impl.cpp | 22 +- core/runtime/common/runtime_context.cpp | 7 +- .../common/runtime_execution_error.cpp | 17 ++ .../common/runtime_execution_error.hpp | 22 ++ .../runtime/common/runtime_instances_pool.cpp | 107 ++++++---- .../runtime/common/runtime_instances_pool.hpp | 54 +++-- .../common/runtime_transaction_error.cpp | 18 -- .../common/runtime_transaction_error.hpp | 23 --- .../common/trie_storage_provider_impl.cpp | 6 +- .../common/trie_storage_provider_impl.hpp | 2 +- .../common/uncompress_code_if_needed.cpp | 2 +- .../common/uncompress_code_if_needed.hpp | 10 +- core/runtime/module.hpp | 3 +- core/runtime/module_factory.hpp | 20 +- core/runtime/module_repository.hpp | 1 + core/runtime/runtime_api/impl/beefy.cpp | 4 +- .../runtime_api/impl/parachain_host.cpp | 1 + .../runtime_api/parachain_host_types.hpp | 8 +- core/runtime/types.hpp | 9 + core/runtime/wavm/core_api_factory_impl.cpp | 19 +- core/runtime/wavm/module.cpp | 16 +- core/runtime/wavm/module.hpp | 17 +- core/runtime/wavm/module_factory_impl.cpp | 18 +- core/runtime/wavm/module_factory_impl.hpp | 2 +- core/runtime/wavm/module_instance.cpp | 4 +- core/scale/CMakeLists.txt | 2 + core/scale/std_variant.hpp | 43 ++++ core/telemetry/CMakeLists.txt | 2 +- core/utils/thread_pool.hpp | 11 +- .../babe/babe_block_validator_test.cpp | 6 +- test/core/consensus/timeline/CMakeLists.txt | 1 + test/core/parachain/pvf_test.cpp | 45 +++-- test/core/runtime/CMakeLists.txt | 4 + test/core/runtime/instance_pool_test.cpp | 80 ++++++++ test/core/runtime/runtime_test_base.hpp | 4 +- .../runtime/trie_storage_provider_test.cpp | 8 +- test/core/runtime/wavm/CMakeLists.txt | 1 + test/external-project-test/src/main.cpp | 2 +- .../application/app_configuration_mock.hpp | 7 + .../mock/core/runtime/module_factory_mock.hpp | 2 +- test/mock/core/runtime/module_mock.hpp | 2 +- 84 files changed, 924 insertions(+), 290 deletions(-) create mode 100644 cmake/toolchain/compiler/gcc-13.cmake create mode 100644 cmake/toolchain/gcc-13_cxx20.cmake create mode 100644 core/parachain/pvf/module_precompiler.cpp create mode 100644 core/parachain/pvf/module_precompiler.hpp create mode 100644 core/runtime/common/runtime_execution_error.cpp create mode 100644 core/runtime/common/runtime_execution_error.hpp delete mode 100644 core/runtime/common/runtime_transaction_error.cpp delete mode 100644 core/runtime/common/runtime_transaction_error.hpp create mode 100644 core/scale/std_variant.hpp create mode 100644 test/core/runtime/instance_pool_test.cpp diff --git a/cmake/clang-format.cmake b/cmake/clang-format.cmake index 7daf973673..b02f7b3a3e 100644 --- a/cmake/clang-format.cmake +++ b/cmake/clang-format.cmake @@ -22,6 +22,9 @@ if(NOT CLANG_FORMAT_BIN) OUTPUT_VARIABLE DEFAULT_CLANG_FORMAT_VERSION ) math(EXPR DEFAULT_CLANG_FORMAT_VERSION "0 + 0${DEFAULT_CLANG_FORMAT_VERSION}") + if (${DEFAULT_CLANG_FORMAT_VERSION} GREATER ${RECOMMENDED_CLANG_FORMAT_VERSION}) + return() + endif() # Try to find newest version foreach(CLANG_FORMAT_VERSION RANGE ${RECOMMENDED_CLANG_FORMAT_VERSION} ${DEFAULT_CLANG_FORMAT_VERSION} -1) find_program(CLANG_FORMAT_BIN_ NAMES clang-format-${CLANG_FORMAT_VERSION}) diff --git a/cmake/toolchain/compiler/gcc-13.cmake b/cmake/toolchain/compiler/gcc-13.cmake new file mode 100644 index 0000000000..8dcbbd4eaf --- /dev/null +++ b/cmake/toolchain/compiler/gcc-13.cmake @@ -0,0 +1,34 @@ +if(DEFINED POLLY_COMPILER_GCC_13_CMAKE_) + return() +else() + set(POLLY_COMPILER_GCC_13_CMAKE_ 1) +endif() + +find_program(CMAKE_C_COMPILER gcc-13) +find_program(CMAKE_CXX_COMPILER g++-13) + +if(NOT CMAKE_C_COMPILER) + fatal_error("gcc-13 not found") +endif() + +if(NOT CMAKE_CXX_COMPILER) + fatal_error("g++-13 not found") +endif() + +set( + CMAKE_C_COMPILER + "${CMAKE_C_COMPILER}" + CACHE + STRING + "C compiler" + FORCE +) + +set( + CMAKE_CXX_COMPILER + "${CMAKE_CXX_COMPILER}" + CACHE + STRING + "C++ compiler" + FORCE +) diff --git a/cmake/toolchain/gcc-13_cxx20.cmake b/cmake/toolchain/gcc-13_cxx20.cmake new file mode 100644 index 0000000000..63abe45f68 --- /dev/null +++ b/cmake/toolchain/gcc-13_cxx20.cmake @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_LIST_DIR}/compiler/gcc-13.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/cxx20.cmake) diff --git a/core/application/app_configuration.hpp b/core/application/app_configuration.hpp index 52972802c4..77a7d89adc 100644 --- a/core/application/app_configuration.hpp +++ b/core/application/app_configuration.hpp @@ -234,6 +234,8 @@ namespace kagome::application { virtual bool purgeWavmCache() const = 0; virtual uint32_t parachainRuntimeInstanceCacheSize() const = 0; + virtual uint32_t parachainPrecompilationThreadNum() const = 0; + virtual bool shouldPrecompileParachainModules() const = 0; enum class OffchainWorkerMode { WhenValidating, Always, Never }; /** diff --git a/core/application/app_state_manager.hpp b/core/application/app_state_manager.hpp index ce4b877754..78f9475a85 100644 --- a/core/application/app_state_manager.hpp +++ b/core/application/app_state_manager.hpp @@ -10,6 +10,26 @@ namespace kagome::application { + // concepts that check if an object has a method that is called by app state + // manager. Deliberately avoid checking that the method returns bool, + // because if there's a method with an appropriate name, and it doesn't return + // bool, we want it to be a compile error instead of silently ignoring it + // because the concept is not satisfied. + template + concept AppStateInjectable = requires(T& t) { t.inject(); }; + template + concept AppStatePreparable = requires(T& t) { t.prepare(); }; + template + concept AppStateStartable = requires(T& t) { t.start(); }; + template + concept AppStateStoppable = requires(T& t) { t.stop(); }; + + // if an object is registered with AppStateManager but has no method + // that is called by AppStateManager, there's probably something wrong + template + concept AppStateControllable = AppStatePreparable || AppStateInjectable + || AppStateStoppable || AppStateStartable; + class AppStateManager : public std::enable_shared_from_this { public: using OnInject = std::function; @@ -55,45 +75,24 @@ namespace kagome::application { */ virtual void atShutdown(OnShutdown &&cb) = 0; - private: - template - struct HasMethodInject : std::false_type {}; - template - struct HasMethodInject : std::true_type {}; - - template - struct HasMethodPrepare : std::false_type {}; - template - struct HasMethodPrepare : std::true_type {}; - - template - struct HasMethodStart : std::false_type {}; - template - struct HasMethodStart : std::true_type {}; - - template - struct HasMethodStop : std::false_type {}; - template - struct HasMethodStop : std::true_type {}; - public: /** * @brief Registration special methods (if any) of object as handlers * for stages of application life-cycle * @param entity is registered entity */ - template + template void takeControl(Controlled &entity) { - if constexpr (HasMethodInject::value) { + if constexpr (AppStateInjectable) { atInject([&entity]() -> bool { return entity.inject(); }); } - if constexpr (HasMethodPrepare::value) { + if constexpr (AppStatePreparable) { atPrepare([&entity]() -> bool { return entity.prepare(); }); } - if constexpr (HasMethodStart::value) { + if constexpr (AppStateStartable) { atLaunch([&entity]() -> bool { return entity.start(); }); } - if constexpr (HasMethodStop::value) { + if constexpr (AppStateStoppable) { atShutdown([&entity]() -> void { return entity.stop(); }); } } diff --git a/core/application/impl/app_configuration_impl.cpp b/core/application/impl/app_configuration_impl.cpp index 995d414a5f..d1b103b187 100644 --- a/core/application/impl/app_configuration_impl.cpp +++ b/core/application/impl/app_configuration_impl.cpp @@ -845,6 +845,10 @@ namespace kagome::application { ("parachain-runtime-instance-cache-size", po::value()->default_value(def_parachain_runtime_instance_cache_size), "Number of parachain runtime instances to keep cached") + ("no-precompile-parachain-modules", po::bool_switch(), "Don't precompile parachain runtime modules at node startup") + ("parachain-precompilation-thread-num", + po::value()->default_value(parachain_precompilation_thread_num_), + "Number of threads that precompile parachain runtime modules at node startup") ; po::options_description benchmark_desc("Benchmark options"); benchmark_desc.add_options() @@ -1402,6 +1406,17 @@ namespace kagome::application { parachain_runtime_instance_cache_size_ = *arg; } + if (!find_argument(vm, "validator") + || find_argument(vm, "no-precompile-parachain-modules")) { + should_precompile_parachain_modules_ = false; + } + + if (auto arg = + find_argument(vm, "parachain-precompilation-thread-num"); + arg.has_value()) { + parachain_precompilation_thread_num_ = *arg; + } + bool offchain_worker_value_error = false; find_argument( vm, diff --git a/core/application/impl/app_configuration_impl.hpp b/core/application/impl/app_configuration_impl.hpp index 374644b0e1..b13f2984ca 100644 --- a/core/application/impl/app_configuration_impl.hpp +++ b/core/application/impl/app_configuration_impl.hpp @@ -18,6 +18,7 @@ namespace rapidjson { #include #include #include +#include #include "log/logger.hpp" @@ -169,6 +170,13 @@ namespace kagome::application { uint32_t parachainRuntimeInstanceCacheSize() const override { return parachain_runtime_instance_cache_size_; } + uint32_t parachainPrecompilationThreadNum() const override { + return parachain_precompilation_thread_num_; + } + + bool shouldPrecompileParachainModules() const override { + return should_precompile_parachain_modules_; + } OffchainWorkerMode offchainWorkerMode() const override { return offchain_worker_mode_; @@ -362,6 +370,9 @@ namespace kagome::application { std::optional benchmark_config_; AllowUnsafeRpc allow_unsafe_rpc_ = AllowUnsafeRpc::kAuto; uint32_t parachain_runtime_instance_cache_size_ = 100; + uint32_t parachain_precompilation_thread_num_ = + std::thread::hardware_concurrency() / 2; + bool should_precompile_parachain_modules_{true}; }; } // namespace kagome::application diff --git a/core/blockchain/impl/block_tree_impl.cpp b/core/blockchain/impl/block_tree_impl.cpp index ce41b3375e..8cea23b1f9 100644 --- a/core/blockchain/impl/block_tree_impl.cpp +++ b/core/blockchain/impl/block_tree_impl.cpp @@ -1044,10 +1044,10 @@ namespace kagome::blockchain { auto header_res = p.header_repo_->getBlockHeader(hash); if (header_res.has_error()) { if (chain.empty()) { - log_->error("cannot retrieve block with hash {}: {}", + log_->error("Cannot retrieve block with hash {}: {}", hash, header_res.error()); - return BlockTreeError::HEADER_NOT_FOUND; + return header_res.error(); } break; } diff --git a/core/blockchain/indexer.hpp b/core/blockchain/indexer.hpp index fce4beb398..428b047c77 100644 --- a/core/blockchain/indexer.hpp +++ b/core/blockchain/indexer.hpp @@ -121,7 +121,7 @@ namespace kagome::blockchain { return outcome::success(); } - Descent descend(const primitives::BlockInfo &from) const { + Descent startDescentFrom(const primitives::BlockInfo &from) const { return {block_tree_, from}; } diff --git a/core/common/empty.hpp b/core/common/empty.hpp index ca9acd03eb..35d92055f5 100644 --- a/core/common/empty.hpp +++ b/core/common/empty.hpp @@ -11,19 +11,19 @@ namespace kagome { /// Special zero-size-type for some things - /// (e.g., unsupported, experimental or empty). + /// (e.g. unsupported, experimental or empty). struct Empty { inline constexpr bool operator==(const Empty &) const { return true; } template - friend inline auto &operator<<(Stream &s, const Empty &) { + friend inline Stream &operator<<(Stream &s, const Empty &) { return s; } template - friend inline auto &operator>>(Stream &s, const Empty &) { + friend inline Stream &operator>>(Stream &s, const Empty &) { return s; } }; diff --git a/core/common/monadic_utils.hpp b/core/common/monadic_utils.hpp index 92635f3702..bfe42e4e75 100644 --- a/core/common/monadic_utils.hpp +++ b/core/common/monadic_utils.hpp @@ -48,11 +48,12 @@ namespace kagome::common { * error. */ template > - outcome::result map_result(const outcome::result &res, const F &f) { + outcome::result map_result(const outcome::result &res, const F &f) { if (res.has_value()) { - return outcome::result{f(res.value())}; + return outcome::result{f(res.value())}; } return res.as_failure(); } @@ -63,7 +64,9 @@ namespace kagome::common { * outcome::result contains a value. Otherwise, just returns the contained * error. */ - template > + template F, + typename R = std::invoke_result_t> outcome::result map_result(outcome::result &&res, const F &f) { if (res.has_value()) { return outcome::result{f(std::move(res.value()))}; diff --git a/core/common/visitor.hpp b/core/common/visitor.hpp index e8f8249f22..6edefebdf6 100644 --- a/core/common/visitor.hpp +++ b/core/common/visitor.hpp @@ -155,4 +155,4 @@ namespace kagome { constexpr decltype(auto) match_in_place(T &&t, Fs &&...fs) { return match(std::forward(t), make_visitor(std::forward(fs)...)); } -} // namespace kagome +} // namespace kagome::common diff --git a/core/consensus/CMakeLists.txt b/core/consensus/CMakeLists.txt index 858aca84a3..0f7daca745 100644 --- a/core/consensus/CMakeLists.txt +++ b/core/consensus/CMakeLists.txt @@ -24,4 +24,3 @@ target_link_libraries(consensus INTERFACE grandpa ) kagome_install(consensus) -kagome_clear_objects(consensus) diff --git a/core/consensus/babe/CMakeLists.txt b/core/consensus/babe/CMakeLists.txt index 97ddca5aba..ab1377fab7 100644 --- a/core/consensus/babe/CMakeLists.txt +++ b/core/consensus/babe/CMakeLists.txt @@ -16,3 +16,4 @@ target_link_libraries(babe consensus_common ) kagome_install(babe) +kagome_clear_objects(babe) diff --git a/core/consensus/babe/impl/babe_config_repository_impl.cpp b/core/consensus/babe/impl/babe_config_repository_impl.cpp index 2bfa2f1218..8d35475133 100644 --- a/core/consensus/babe/impl/babe_config_repository_impl.cpp +++ b/core/consensus/babe/impl/babe_config_repository_impl.cpp @@ -234,7 +234,7 @@ namespace kagome::consensus::babe { BabeConfigRepositoryImpl::config(Indexer &indexer_, const primitives::BlockInfo &block, bool next_epoch) const { - auto descent = indexer_.descend(block); + auto descent = indexer_.startDescentFrom(block); outcome::result cb_res = outcome::success(); auto cb = [&](std::optional prev, size_t i_first, diff --git a/core/consensus/grandpa/CMakeLists.txt b/core/consensus/grandpa/CMakeLists.txt index 80535c917d..72ec996b5b 100644 --- a/core/consensus/grandpa/CMakeLists.txt +++ b/core/consensus/grandpa/CMakeLists.txt @@ -19,22 +19,8 @@ add_library(grandpa voting_round_error.cpp ) target_link_libraries(grandpa -# mp_utils -# ed25519_types -# Boost::boost -# schnorrkel_crust::schnorrkel_crust -# transaction_pool_error -# hasher -# vrf_provider logger -# scale::scale -# primitives -# blob -# outcome -# p2p::p2p_peer_id -# storage metrics -# blockchain -# telemetry ) kagome_install(grandpa) +kagome_clear_objects(grandpa) diff --git a/core/consensus/grandpa/impl/authority_manager_impl.cpp b/core/consensus/grandpa/impl/authority_manager_impl.cpp index b9ad3d3ee5..27c0c703d2 100644 --- a/core/consensus/grandpa/impl/authority_manager_impl.cpp +++ b/core/consensus/grandpa/impl/authority_manager_impl.cpp @@ -85,7 +85,7 @@ namespace kagome::consensus::grandpa { outcome::result> AuthorityManagerImpl::authoritiesOutcome(const primitives::BlockInfo &block, bool next) const { - auto descent = indexer_.descend(block); + auto descent = indexer_.startDescentFrom(block); outcome::result cb_res = outcome::success(); auto cb = [&](std::optional prev, size_t i_first, diff --git a/core/consensus/timeline/CMakeLists.txt b/core/consensus/timeline/CMakeLists.txt index fde6ddf127..75a5f7aec5 100644 --- a/core/consensus/timeline/CMakeLists.txt +++ b/core/consensus/timeline/CMakeLists.txt @@ -17,5 +17,8 @@ add_library(timeline ) target_link_libraries(timeline logger + telemetry + network ) kagome_install(timeline) +kagome_clear_objects(timeline) diff --git a/core/host_api/impl/storage_extension.cpp b/core/host_api/impl/storage_extension.cpp index 96e5a2e8b1..4eaf0ddb85 100644 --- a/core/host_api/impl/storage_extension.cpp +++ b/core/host_api/impl/storage_extension.cpp @@ -13,7 +13,7 @@ #include "host_api/impl/storage_util.hpp" #include "log/formatters/optional.hpp" #include "log/trace_macros.hpp" -#include "runtime/common/runtime_transaction_error.hpp" +#include "runtime/common/runtime_execution_error.hpp" #include "runtime/memory_provider.hpp" #include "runtime/ptr_size.hpp" #include "runtime/trie_storage_provider.hpp" @@ -42,7 +42,7 @@ namespace kagome::host_api { if (auto res = storage_provider_->rollbackTransaction(); res.has_error()) { if (res.error() - != runtime::RuntimeTransactionError::NO_TRANSACTIONS_WERE_STARTED) { + != runtime::RuntimeExecutionError::NO_TRANSACTIONS_WERE_STARTED) { logger_->error(res.error()); } break; diff --git a/core/injector/application_injector.cpp b/core/injector/application_injector.cpp index 4224e8ee07..5d71c60832 100644 --- a/core/injector/application_injector.cpp +++ b/core/injector/application_injector.cpp @@ -8,7 +8,7 @@ #define BOOST_DI_CFG_DIAGNOSTICS_LEVEL 2 #define BOOST_DI_CFG_CTOR_LIMIT_SIZE \ - 32 // TODO(Harrm): check how it influences on compilation time + 24 // TODO(Harrm): check how it influences on compilation time #include #include @@ -128,6 +128,7 @@ #include "parachain/availability/recovery/recovery_impl.hpp" #include "parachain/availability/store/store_impl.hpp" #include "parachain/backing/store_impl.hpp" +#include "parachain/pvf/module_precompiler.hpp" #include "parachain/pvf/pvf_impl.hpp" #include "parachain/validator/impl/parachain_observer_impl.hpp" #include "parachain/validator/parachain_processor.hpp" @@ -462,7 +463,10 @@ namespace { makeWavmInjector(method), makeBinaryenInjector(method), bind_by_lambda([](const auto &injector) { - return std::make_shared(); + auto module_factory = + injector.template create>(); + return std::make_shared( + module_factory); }), di::bind.template to(), bind_by_lambda([method](const auto &injector) { @@ -555,6 +559,12 @@ namespace { libp2p::protocol::PingConfig ping_config{}; host_api::OffchainExtensionConfig offchain_ext_config{ config->isOffchainIndexingEnabled()}; + parachain::PvfImpl::Config pvf_config{ + .precompile_modules = config->shouldPrecompileParachainModules(), + .runtime_instance_cache_size = + config->parachainRuntimeInstanceCacheSize(), + .precompile_threads_num = config->parachainPrecompilationThreadNum(), + }; // clang-format off return di:: @@ -566,6 +576,7 @@ namespace { useConfig(tp_pool_limits), useConfig(ping_config), useConfig(offchain_ext_config), + useConfig(pvf_config), // inherit host injector libp2p::injector::makeHostInjector( diff --git a/core/log/configurator.cpp b/core/log/configurator.cpp index c67fb83335..e0e0055147 100644 --- a/core/log/configurator.cpp +++ b/core/log/configurator.cpp @@ -64,6 +64,8 @@ namespace kagome::log { children: - name: voting_round - name: parachain + children: + - name: pvf_executor - name: dispute - name: runtime children: diff --git a/core/network/CMakeLists.txt b/core/network/CMakeLists.txt index aa86a644f3..9f789cf101 100644 --- a/core/network/CMakeLists.txt +++ b/core/network/CMakeLists.txt @@ -58,3 +58,4 @@ target_link_libraries(network p2p::p2p_loopback_stream ) kagome_clear_objects(network) +kagome_install(network) \ No newline at end of file diff --git a/core/network/beefy/beefy.cpp b/core/network/beefy/beefy.cpp index 7327620b40..cac0a1c620 100644 --- a/core/network/beefy/beefy.cpp +++ b/core/network/beefy/beefy.cpp @@ -16,7 +16,7 @@ #include "crypto/crypto_store/session_keys.hpp" #include "metrics/histogram_timer.hpp" #include "network/beefy/protocol.hpp" -#include "runtime/common/runtime_transaction_error.hpp" +#include "runtime/common/runtime_execution_error.hpp" #include "runtime/runtime_api/beefy.hpp" #include "storage/spaced_storage.hpp" #include "utils/block_number_key.hpp" @@ -232,7 +232,7 @@ namespace kagome::network { // bug: beefy pallet doesn't produce digest with first validators OUTCOME_TRY(validators, beefy_api_->validatorSet(info.hash)); if (not validators) { - return runtime::RuntimeTransactionError::EXPORT_FUNCTION_NOT_FOUND; + return runtime::RuntimeExecutionError::EXPORT_FUNCTION_NOT_FOUND; } return std::make_pair(info.number, std::move(*validators)); } diff --git a/core/network/helpers/scale_message_read_writer.hpp b/core/network/helpers/scale_message_read_writer.hpp index 8432a580e8..d605d81848 100644 --- a/core/network/helpers/scale_message_read_writer.hpp +++ b/core/network/helpers/scale_message_read_writer.hpp @@ -66,7 +66,7 @@ namespace kagome::network { template void write(const MsgType &msg, libp2p::basic::Writer::WriteCallbackFunc cb) const { - auto encoded_msg_res = scale::encode(msg); + auto encoded_msg_res = ::scale::encode(msg); if (!encoded_msg_res) { return cb(encoded_msg_res.error()); } diff --git a/core/network/protobuf/CMakeLists.txt b/core/network/protobuf/CMakeLists.txt index 044af64474..b173323e98 100644 --- a/core/network/protobuf/CMakeLists.txt +++ b/core/network/protobuf/CMakeLists.txt @@ -13,6 +13,7 @@ target_include_directories(node_api_proto PUBLIC # required for compiling proto targets $ ) +kagome_install(node_api_proto) add_proto_library(light_api_proto light.v1.proto @@ -23,3 +24,4 @@ target_include_directories(light_api_proto PUBLIC # required for compiling proto targets $ ) +kagome_install(light_api_proto) diff --git a/core/network/types/collator_messages.hpp b/core/network/types/collator_messages.hpp index f1c1157468..112450dbb3 100644 --- a/core/network/types/collator_messages.hpp +++ b/core/network/types/collator_messages.hpp @@ -132,7 +132,7 @@ namespace kagome::network { const Hash &hash(const crypto::Hasher &hasher) const { if (not hash_.has_value()) { hash_.emplace(hasher.blake2b_256( - scale::encode(std::tie(descriptor, commitments_hash)).value())); + ::scale::encode(std::tie(descriptor, commitments_hash)).value())); } return hash_.value(); } @@ -523,7 +523,7 @@ namespace kagome::network { auto commitments_hash = hasher.blake2b_256(scale::encode(receipt.commitments).value()); return hasher.blake2b_256( - scale::encode(std::tie(receipt.descriptor, commitments_hash)).value()); + ::scale::encode(std::tie(receipt.descriptor, commitments_hash)).value()); } inline CandidateHash candidateHash(const crypto::Hasher &hasher, diff --git a/core/parachain/CMakeLists.txt b/core/parachain/CMakeLists.txt index a4ff265a18..8cb7897d5d 100644 --- a/core/parachain/CMakeLists.txt +++ b/core/parachain/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(validator_parachain backing/store_impl.cpp pvf/precheck.cpp pvf/pvf_impl.cpp + pvf/module_precompiler.cpp validator/impl/parachain_observer_impl.cpp validator/impl/parachain_processor.cpp validator/signer.cpp diff --git a/core/parachain/availability/bitfield/signer.cpp b/core/parachain/availability/bitfield/signer.cpp index 84616a60b2..d21ed65d60 100644 --- a/core/parachain/availability/bitfield/signer.cpp +++ b/core/parachain/availability/bitfield/signer.cpp @@ -104,7 +104,7 @@ namespace kagome::parachain { parachain_api_->session_info(relay_parent, signer->getSessionIndex())); candidates.reserve(cores.size()); for (auto &core : cores) { - if (auto occupied = boost::get(&core)) { + if (auto occupied = std::get_if(&core)) { candidates.emplace_back(occupied->candidate_hash); fetch_->fetch(signer->validatorIndex(), *occupied, *session); } else { diff --git a/core/parachain/availability/bitfield/store_impl.cpp b/core/parachain/availability/bitfield/store_impl.cpp index 2d136839ea..270c5a9e6d 100644 --- a/core/parachain/availability/bitfield/store_impl.cpp +++ b/core/parachain/availability/bitfield/store_impl.cpp @@ -59,7 +59,7 @@ namespace kagome::parachain { bool skip = false; for (size_t ix = 0; ix < cores.size(); ++ix) { auto &core = cores[ix]; - if (is_type(core)) { + if (std::holds_alternative(core)) { continue; } diff --git a/core/parachain/availability/chunks.hpp b/core/parachain/availability/chunks.hpp index f6c0230ef2..6e4b6838e1 100644 --- a/core/parachain/availability/chunks.hpp +++ b/core/parachain/availability/chunks.hpp @@ -23,7 +23,7 @@ namespace kagome::parachain { inline outcome::result> toChunks( size_t validators, const runtime::AvailableData &data) { - OUTCOME_TRY(message, scale::encode(data)); + OUTCOME_TRY(message, ::scale::encode(data)); auto create_result = ec_cpp::create(validators); if (ec_cpp::resultHasError(create_result)) { diff --git a/core/parachain/pvf/module_precompiler.cpp b/core/parachain/pvf/module_precompiler.cpp new file mode 100644 index 0000000000..88769c8d8f --- /dev/null +++ b/core/parachain/pvf/module_precompiler.cpp @@ -0,0 +1,189 @@ +/** + * Copyright Quadrivium LLC All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "parachain/pvf/module_precompiler.hpp" + +#include +#include +#include + +#include "runtime/common/runtime_execution_error.hpp" +#include "runtime/common/runtime_instances_pool.hpp" +#include "runtime/runtime_api/parachain_host.hpp" +#include "runtime/runtime_api/parachain_host_types.hpp" + +namespace kagome::parachain { + + struct ParachainCore { + runtime::CoreState state; + }; + + ModulePrecompiler::ModulePrecompiler( + const kagome::parachain::ModulePrecompiler::Config &config, + std::shared_ptr parachain_api, + std::shared_ptr runtime_cache, + std::shared_ptr hasher) + : config_{config}, + parachain_api_{parachain_api}, + runtime_cache_{runtime_cache}, + hasher_{hasher} { + if (getThreadsNum() > std::thread::hardware_concurrency() - 1) { + SL_WARN( + log_, + "The number of threads assigned for parachain runtime module " + "pre-compilation is greater than (the number of hardware cores - 1). " + "This is most likely inefficient."); + } + } + + struct ModulePrecompiler::PrecompilationStats { + const size_t total_count{}; + std::atomic_int occupied_precompiled_count{}; + std::atomic_int scheduled_precompiled_count{}; + std::atomic_int total_code_size{}; + }; + + std::optional get_para_id(runtime::CoreState core) { + return visit_in_place( + core, + [](const runtime::OccupiedCore &core) mutable + -> std::optional { + return core.candidate_descriptor.para_id; + }, + [](const runtime::ScheduledCore &core) mutable + -> std::optional { return core.para_id; }, + [](runtime::FreeCore) -> std::optional { + return std::nullopt; + }); + } + + outcome::result ModulePrecompiler::precompileModulesAt( + const primitives::BlockHash &last_finalized) { + auto cores_res = parachain_api_->availability_cores(last_finalized); + if (cores_res.has_error() + && cores_res.error() + == runtime::RuntimeExecutionError::EXPORT_FUNCTION_NOT_FOUND) { + SL_WARN(log_, + "Failed to warm up PVF executor runtime module cache, since " + "ParachainHost API is not present in the runtime at block {}", + last_finalized); + return outcome::success(); + } + OUTCOME_TRY(cores, cores_res); + SL_DEBUG(log_, + "Warming up PVF executor runtime instance cache at block {}", + last_finalized); + PrecompilationStats stats{ + .total_count = cores.size(), + }; + auto start = std::chrono::steady_clock::now(); + + std::mutex cores_queue_mutex; + std::vector threads; + for (size_t i = 0; i < config_.precompile_threads_num; i++) { + auto compilation_worker = + [self = shared_from_this(), + &cores_queue_mutex, + &cores, + &stats, + &last_finalized]() mutable -> outcome::result { + while (true) { + runtime::CoreState core; + { + std::scoped_lock l{cores_queue_mutex}; + if (cores.empty()) { + break; + } + core = cores.back(); + cores.pop_back(); + } + auto res = self->precompileModulesForCore( + stats, last_finalized, ParachainCore{core}); + if (!res) { + using namespace std::string_literals; + auto id = get_para_id(core); + SL_ERROR(self->log_, + "Failed to precompile parachain module for {} parachain " + "core: {}", + id ? std::to_string(*id) : "empty"s, + res.error()); + } + } + return outcome::success(); + }; + threads.emplace_back(compilation_worker); + } + + for (auto &t : threads) { + t.join(); + } + + auto end = std::chrono::steady_clock::now(); + double time_taken = + static_cast( + std::chrono::duration_cast(end - start) + .count()) + / 1e3; + SL_VERBOSE(log_, + "Precompiled runtime instances for {} occupied parachain " + "cores and {} scheduled parachain cores. Total code size is " + "{}, time taken is {}s", + stats.occupied_precompiled_count.load(), + stats.scheduled_precompiled_count.load(), + stats.total_code_size.load(), + time_taken); + return outcome::success(); + } + + outcome::result ModulePrecompiler::precompileModulesForCore( + PrecompilationStats &stats, + const primitives::BlockHash &last_finalized, + const ParachainCore &_core) { + auto &core = _core.state; + if (std::holds_alternative(core)) { + return outcome::success(); + + } else if (std::holds_alternative(core)) { + SL_TRACE(log_, "Precompile for occupied availability core"); + stats.occupied_precompiled_count++; + } else if (std::holds_alternative(core)) { + SL_TRACE(log_, "Precompile for scheduled availability core"); + stats.scheduled_precompiled_count++; + } + // since we eliminated empty core option earlier + auto para_id = get_para_id(core).value(); + OUTCOME_TRY(code_opt, + parachain_api_->validation_code( + last_finalized, + para_id, + runtime::OccupiedCoreAssumption::Included)); + if (!code_opt) { + SL_WARN(log_, + "No validation code found for parachain {} with 'included' " + "occupied assumption", + para_id); + return outcome::success(); + } + auto &code = *code_opt; + auto hash = hasher_->blake2b_256(code); + SL_DEBUG(log_, + "Validation code for parachain {} has size {} and hash {}", + para_id, + code.size(), + hash); + stats.total_code_size += code.size(); + OUTCOME_TRY(runtime_cache_->instantiateFromCode(hash, code)); + SL_DEBUG(log_, + "Instantiated runtime instance with code hash {} for parachain " + "{}, {} left", + hash, + para_id, + stats.total_count - stats.occupied_precompiled_count + - stats.scheduled_precompiled_count); + + return outcome::success(); + } + +} // namespace kagome::parachain diff --git a/core/parachain/pvf/module_precompiler.hpp b/core/parachain/pvf/module_precompiler.hpp new file mode 100644 index 0000000000..57e7b7e6c6 --- /dev/null +++ b/core/parachain/pvf/module_precompiler.hpp @@ -0,0 +1,59 @@ +/** + * Copyright Quadrivium LLC All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "log/logger.hpp" +#include "outcome/outcome.hpp" +#include "primitives/block_id.hpp" + +namespace kagome::crypto { + class Hasher; +} + +namespace kagome::runtime { + class ParachainHost; + class RuntimeInstancesPool; +} // namespace kagome::runtime + +namespace kagome::parachain { + struct ParachainCore; + + class ModulePrecompiler + : public std::enable_shared_from_this { + public: + struct Config { + unsigned precompile_threads_num; + }; + + ModulePrecompiler( + const Config &config, + std::shared_ptr parachain_api, + std::shared_ptr runtime_cache, + std::shared_ptr hasher); + + outcome::result precompileModulesAt( + const primitives::BlockHash &last_finalized); + + size_t getThreadsNum() const { + return config_.precompile_threads_num; + } + + private: + struct PrecompilationStats; + + outcome::result precompileModulesForCore( + PrecompilationStats &stats, + const primitives::BlockHash &last_finalized, + const ParachainCore &core); + + Config config_; + std::shared_ptr parachain_api_; + std::shared_ptr runtime_cache_; + std::shared_ptr hasher_; + + log::Logger log_ = log::createLogger("ModulePrecompiler", "pvf_executor"); + }; +} // namespace kagome::parachain diff --git a/core/parachain/pvf/pvf_impl.cpp b/core/parachain/pvf/pvf_impl.cpp index 4055f48a16..b1c326d2cf 100644 --- a/core/parachain/pvf/pvf_impl.cpp +++ b/core/parachain/pvf/pvf_impl.cpp @@ -7,7 +7,12 @@ #include "parachain/pvf/pvf_impl.hpp" #include "application/app_configuration.hpp" +#include "application/app_state_manager.hpp" +#include "blockchain/block_tree.hpp" +#include "common/visitor.hpp" #include "metrics/histogram_timer.hpp" +#include "parachain/pvf/module_precompiler.hpp" +#include "runtime/common/runtime_execution_error.hpp" #include "runtime/common/runtime_instances_pool.hpp" #include "runtime/common/uncompress_code_if_needed.hpp" #include "runtime/executor.hpp" @@ -15,6 +20,7 @@ #include "runtime/module_factory.hpp" #include "runtime/module_repository.hpp" #include "runtime/runtime_code_provider.hpp" +#include "scale/std_variant.hpp" OUTCOME_CPP_DEFINE_CATEGORY(kagome::parachain, PvfError, e) { using kagome::parachain::PvfError; @@ -108,26 +114,50 @@ namespace kagome::parachain { }; PvfImpl::PvfImpl( + const Config &config, std::shared_ptr hasher, std::shared_ptr module_factory, std::shared_ptr runtime_properties_cache, - std::shared_ptr - block_header_repository, + std::shared_ptr block_tree, std::shared_ptr sr25519_provider, std::shared_ptr parachain_api, std::shared_ptr executor, std::shared_ptr ctx_factory, - std::shared_ptr config) - : hasher_{std::move(hasher)}, + std::shared_ptr state_manager) + : config_{config}, + hasher_{std::move(hasher)}, runtime_properties_cache_{std::move(runtime_properties_cache)}, - block_header_repository_{std::move(block_header_repository)}, + block_tree_{std::move(block_tree)}, sr25519_provider_{std::move(sr25519_provider)}, parachain_api_{std::move(parachain_api)}, executor_{std::move(executor)}, ctx_factory_{std::move(ctx_factory)}, - log_{log::createLogger("Pvf")}, + log_{log::createLogger("PVF Executor", "pvf_executor")}, runtime_cache_{std::make_shared( - module_factory, config->parachainRuntimeInstanceCacheSize())} {} + module_factory, config.runtime_instance_cache_size)}, + precompiler_{std::make_shared( + ModulePrecompiler::Config{config_.precompile_threads_num}, + parachain_api_, + runtime_cache_, + hasher_)} { + state_manager->takeControl(*this); + } + + bool PvfImpl::prepare() { + if (config_.precompile_modules) { + std::thread t{[self = shared_from_this()]() { + auto res = self->precompiler_->precompileModulesAt( + self->block_tree_->getLastFinalized().hash); + if (!res) { + SL_ERROR(self->log_, + "Parachain module precompilation failed: {}", + res.error()); + } + }}; + t.detach(); + } + return true; + } outcome::result PvfImpl::pvfValidate( const PersistedValidationData &data, @@ -228,7 +258,8 @@ namespace kagome::parachain { const common::Hash256 &code_hash, const ParachainRuntime &code_zstd, const ValidationParams ¶ms) const { - OUTCOME_TRY(instance, runtime_cache_->instantiate(code_hash, code_zstd)); + OUTCOME_TRY(instance, + runtime_cache_->instantiateFromCode(code_hash, code_zstd)); runtime::RuntimeContext::ContextParams executor_params{}; auto &parent_hash = receipt.descriptor.relay_parent; diff --git a/core/parachain/pvf/pvf_impl.hpp b/core/parachain/pvf/pvf_impl.hpp index 9facdeb84c..027b774134 100644 --- a/core/parachain/pvf/pvf_impl.hpp +++ b/core/parachain/pvf/pvf_impl.hpp @@ -8,7 +8,6 @@ #include "parachain/pvf/pvf.hpp" -#include "blockchain/block_header_repository.hpp" #include "crypto/sr25519_provider.hpp" #include "log/logger.hpp" #include "runtime/runtime_api/parachain_host.hpp" @@ -16,6 +15,11 @@ namespace kagome::application { class AppConfiguration; + class AppStateManager; +} // namespace kagome::application + +namespace kagome::blockchain { + class BlockTree; } namespace kagome::runtime { @@ -43,6 +47,8 @@ namespace kagome::parachain { OUTCOME_HPP_DECLARE_ERROR(kagome::parachain, PvfError) namespace kagome::parachain { + class ModulePrecompiler; + struct ValidationParams; struct ValidationResult { @@ -56,19 +62,27 @@ namespace kagome::parachain { BlockNumber hrmp_watermark; }; - class PvfImpl : public Pvf { + class PvfImpl : public Pvf, public std::enable_shared_from_this { public: - PvfImpl(std::shared_ptr hasher, + struct Config { + bool precompile_modules; + size_t runtime_instance_cache_size{16}; + unsigned precompile_threads_num{1}; + }; + + PvfImpl(const Config &config, + std::shared_ptr hasher, std::shared_ptr module_factory, std::shared_ptr runtime_properties_cache, - std::shared_ptr - block_header_repository, + std::shared_ptr block_tree, std::shared_ptr sr25519_provider, std::shared_ptr parachain_api, std::shared_ptr executor, std::shared_ptr ctx_factory, - std::shared_ptr config); + std::shared_ptr app_state_manager); + + bool prepare(); outcome::result pvfSync(const CandidateReceipt &receipt, const ParachainBlock &pov) const override; @@ -84,17 +98,20 @@ namespace kagome::parachain { outcome::result> findData(const CandidateDescriptor &descriptor) const; + outcome::result callWasm( const CandidateReceipt &receipt, const common::Hash256 &code_hash, const ParachainRuntime &code_zstd, const ValidationParams ¶ms) const; + outcome::result fromOutputs( const CandidateReceipt &receipt, ValidationResult &&result) const; + Config config_; std::shared_ptr hasher_; std::shared_ptr runtime_properties_cache_; - std::shared_ptr block_header_repository_; + std::shared_ptr block_tree_; std::shared_ptr sr25519_provider_; std::shared_ptr parachain_api_; std::shared_ptr executor_; @@ -102,5 +119,6 @@ namespace kagome::parachain { log::Logger log_; std::shared_ptr runtime_cache_; + std::shared_ptr precompiler_; }; } // namespace kagome::parachain diff --git a/core/parachain/types.hpp b/core/parachain/types.hpp index cd13dd2a64..90fc897378 100644 --- a/core/parachain/types.hpp +++ b/core/parachain/types.hpp @@ -88,7 +88,7 @@ namespace kagome::parachain { auto signable() { constexpr std::array kMagic{'V', 'C', 'P', 'C'}; - return scale::encode(std::make_tuple(kMagic, *this)).value(); + return ::scale::encode(std::make_tuple(kMagic, *this)).value(); } }; } // namespace kagome::parachain diff --git a/core/parachain/validator/impl/parachain_processor.cpp b/core/parachain/validator/impl/parachain_processor.cpp index 5cb5b11e32..9baf31de50 100644 --- a/core/parachain/validator/impl/parachain_processor.cpp +++ b/core/parachain/validator/impl/parachain_processor.cpp @@ -335,7 +335,7 @@ namespace kagome::parachain { core_index < static_cast(cores.size()); ++core_index) { if (const auto *scheduled = - boost::get(&cores[core_index])) { + std::get_if(&cores[core_index])) { const auto group_index = group_rotation_info.groupForCore(core_index, n_cores); if (group_index < validator_groups.size()) { diff --git a/core/runtime/binaryen/core_api_factory_impl.cpp b/core/runtime/binaryen/core_api_factory_impl.cpp index 02d3e98a45..71ea9908a3 100644 --- a/core/runtime/binaryen/core_api_factory_impl.cpp +++ b/core/runtime/binaryen/core_api_factory_impl.cpp @@ -34,10 +34,10 @@ namespace kagome::runtime::binaryen { const primitives::BlockInfo &, const storage::trie::RootHash &) override { if (instance_ == nullptr) { - OUTCOME_TRY( - module, - ModuleImpl::createFromCode(code_, env_factory_, code_hash_)); - OUTCOME_TRY(inst, module->instantiate()); + auto module_res = + ModuleImpl::createFromCode(code_, env_factory_, code_hash_); + if (!module_res) return make_error_code(module_res.error()); + auto inst = module_res.value()->instantiate(); instance_ = std::move(inst); } return instance_; diff --git a/core/runtime/binaryen/module/module_factory_impl.cpp b/core/runtime/binaryen/module/module_factory_impl.cpp index d7c240d18f..9e8724550d 100644 --- a/core/runtime/binaryen/module/module_factory_impl.cpp +++ b/core/runtime/binaryen/module/module_factory_impl.cpp @@ -27,7 +27,7 @@ namespace kagome::runtime::binaryen { BOOST_ASSERT(storage_ != nullptr); } - outcome::result> ModuleFactoryImpl::make( + outcome::result, CompilationError> ModuleFactoryImpl::make( common::BufferView code) const { std::vector code_vec{code.begin(), code.end()}; OUTCOME_TRY(module, diff --git a/core/runtime/binaryen/module/module_factory_impl.hpp b/core/runtime/binaryen/module/module_factory_impl.hpp index 523c853a2e..a60d4ea4f4 100644 --- a/core/runtime/binaryen/module/module_factory_impl.hpp +++ b/core/runtime/binaryen/module/module_factory_impl.hpp @@ -38,7 +38,7 @@ namespace kagome::runtime::binaryen { std::shared_ptr storage, std::shared_ptr hasher); - outcome::result> make( + outcome::result, CompilationError> make( common::BufferView code) const override; private: diff --git a/core/runtime/binaryen/module/module_impl.cpp b/core/runtime/binaryen/module/module_impl.cpp index 463b79134f..c272090b4d 100644 --- a/core/runtime/binaryen/module/module_impl.cpp +++ b/core/runtime/binaryen/module/module_impl.cpp @@ -44,7 +44,8 @@ namespace kagome::runtime::binaryen { BOOST_ASSERT(env_factory_ != nullptr); } - outcome::result> ModuleImpl::createFromCode( + outcome::result, CompilationError> + ModuleImpl::createFromCode( const std::vector &code, std::shared_ptr env_factory, const common::Hash256 &code_hash) { @@ -52,7 +53,8 @@ namespace kagome::runtime::binaryen { // that nolint suppresses false positive in a library function // NOLINTNEXTLINE(clang-analyzer-core.NonNullParamChecker) if (code.empty()) { - return Error::EMPTY_STATE_CODE; + return CompilationError{ + "Empty WASM code supplied to binaryen's ModuleImpl::createFromCode"}; } auto module = std::make_unique(); @@ -67,8 +69,11 @@ namespace kagome::runtime::binaryen { } catch (wasm::ParseException &e) { std::ostringstream msg; e.dump(msg); - log->error(msg.str()); - return Error::INVALID_STATE_CODE; + log->warn(msg.str()); + return CompilationError{ + fmt::format("Invalid WASM code supplied to binaryen's " + "ModuleImpl::createFromCode: {}", + msg.str())}; } } @@ -78,8 +83,7 @@ namespace kagome::runtime::binaryen { std::move(module), std::move(env_factory), code_hash); } - outcome::result> ModuleImpl::instantiate() - const { + std::shared_ptr ModuleImpl::instantiate() const { auto env = env_factory_->make(); return std::make_shared( std::move(env.env), shared_from_this(), env.rei, code_hash_); diff --git a/core/runtime/binaryen/module/module_impl.hpp b/core/runtime/binaryen/module/module_impl.hpp index 4826c33d4b..d478acca97 100644 --- a/core/runtime/binaryen/module/module_impl.hpp +++ b/core/runtime/binaryen/module/module_impl.hpp @@ -10,6 +10,7 @@ #include "common/buffer.hpp" #include "runtime/binaryen/module/module_instance_impl.hpp" +#include "runtime/module_factory.hpp" #include "runtime/trie_storage_provider.hpp" namespace wasm { @@ -43,13 +44,13 @@ namespace kagome::runtime::binaryen { ~ModuleImpl() override = default; - static outcome::result> createFromCode( + static outcome::result, CompilationError> + createFromCode( const std::vector &code, std::shared_ptr env_factory_, const common::Hash256 &code_hash); - outcome::result> instantiate() - const override; + std::shared_ptr instantiate() const override; ModuleImpl(std::unique_ptr &&module, std::shared_ptr env_factory, diff --git a/core/runtime/binaryen/module/module_instance_impl.cpp b/core/runtime/binaryen/module/module_instance_impl.cpp index 9dd93d6ae1..00009885df 100644 --- a/core/runtime/binaryen/module/module_instance_impl.cpp +++ b/core/runtime/binaryen/module/module_instance_impl.cpp @@ -13,7 +13,7 @@ #include "runtime/binaryen/memory_impl.hpp" #include "runtime/binaryen/module/module_impl.hpp" -#include "runtime/common/runtime_transaction_error.hpp" +#include "runtime/common/runtime_execution_error.hpp" #include "runtime/memory_provider.hpp" #include @@ -110,7 +110,7 @@ namespace kagome::runtime::binaryen { module_instance_->wasm.getExportOrNull(wasm::Name{name.data()}); nullptr == res) { SL_DEBUG(logger_, "The requested function {} not found", name); - return RuntimeTransactionError::EXPORT_FUNCTION_NOT_FOUND; + return RuntimeExecutionError::EXPORT_FUNCTION_NOT_FOUND; } try { diff --git a/core/runtime/common/CMakeLists.txt b/core/runtime/common/CMakeLists.txt index 94746f315e..9f54a91901 100644 --- a/core/runtime/common/CMakeLists.txt +++ b/core/runtime/common/CMakeLists.txt @@ -31,7 +31,7 @@ target_link_libraries(uncompress_if_needed kagome_install(uncompress_if_needed) add_library(runtime_transaction_error - runtime_transaction_error.cpp + runtime_execution_error.cpp ) target_link_libraries(runtime_transaction_error outcome @@ -62,6 +62,9 @@ add_library(module_repository runtime_instances_pool.cpp) target_link_libraries(module_repository outcome + uncompress_if_needed + blob + executor ) kagome_install(module_repository) diff --git a/core/runtime/common/module_repository_impl.cpp b/core/runtime/common/module_repository_impl.cpp index 077d16a491..bedbb56a2d 100644 --- a/core/runtime/common/module_repository_impl.cpp +++ b/core/runtime/common/module_repository_impl.cpp @@ -15,6 +15,15 @@ #include "runtime/runtime_code_provider.hpp" #include "runtime/runtime_upgrade_tracker.hpp" +OUTCOME_CPP_DEFINE_CATEGORY(kagome::runtime, Error, e) { + using E = kagome::runtime::Error; + switch (e) { + case E::COMPILATION_FAILED: + return "Runtime module compilation failed"; + } + return "Unknown module repository error"; +} + namespace kagome::runtime { using kagome::primitives::ThreadNumber; using soralog::util::getThreadNumber; @@ -63,13 +72,18 @@ namespace kagome::runtime { if (not code.has_value()) { return code.as_failure(); } - OUTCOME_TRY(new_module, module_factory_->make(code.value())); - runtime_instances_pool_->putModule(state, std::move(new_module)); + auto new_module_res = module_factory_->make(code.value()); + if (!new_module_res) { + return make_error_code(new_module_res.error()); + } + runtime_instances_pool_->putModule(state, + std::move(new_module_res.value())); } } - // Try acquire instance (instantiate if needed) - OUTCOME_TRY(runtime_instance, runtime_instances_pool_->tryAcquire(state)); + // Try to acquire an instance (instantiate if needed) + OUTCOME_TRY(runtime_instance, + runtime_instances_pool_->instantiateFromState(state)); KAGOME_PROFILE_END(module_retrieval) return runtime_instance; diff --git a/core/runtime/common/runtime_context.cpp b/core/runtime/common/runtime_context.cpp index 30fa8eb99c..a1f31d71ae 100644 --- a/core/runtime/common/runtime_context.cpp +++ b/core/runtime/common/runtime_context.cpp @@ -37,8 +37,11 @@ namespace kagome::runtime { ContextParams params) { common::Buffer code; OUTCOME_TRY(runtime::uncompressCodeIfNeeded(code_zstd, code)); - OUTCOME_TRY(runtime_module, module_factory.make(code)); - OUTCOME_TRY(instance, runtime_module->instantiate()); + auto runtime_module_res = module_factory.make(code); + if (!runtime_module_res) { + return Error::COMPILATION_FAILED; + } + auto instance = runtime_module_res.value()->instantiate(); runtime::RuntimeContext ctx{ instance, }; diff --git a/core/runtime/common/runtime_execution_error.cpp b/core/runtime/common/runtime_execution_error.cpp new file mode 100644 index 0000000000..cc7b7d8645 --- /dev/null +++ b/core/runtime/common/runtime_execution_error.cpp @@ -0,0 +1,17 @@ +/** +* Copyright Quadrivium LLC All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0 +*/ + +#include "runtime/common/runtime_execution_error.hpp" + +OUTCOME_CPP_DEFINE_CATEGORY(kagome::runtime, RuntimeExecutionError, e) { + using E = kagome::runtime::RuntimeExecutionError; + switch (e) { + case E::NO_TRANSACTIONS_WERE_STARTED: + return "No storage transactions were started"; + case E::EXPORT_FUNCTION_NOT_FOUND: + return "Export function not found"; + } + return "Unknown RuntimeExecutionError"; +} diff --git a/core/runtime/common/runtime_execution_error.hpp b/core/runtime/common/runtime_execution_error.hpp new file mode 100644 index 0000000000..7e438bfee6 --- /dev/null +++ b/core/runtime/common/runtime_execution_error.hpp @@ -0,0 +1,22 @@ +/** +* Copyright Quadrivium LLC All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0 +*/ + +#pragma once + +#include + +namespace kagome::runtime { + + /** + * @brief RuntimeExecutionError enum provides error codes for storage + * transactions mechanism + */ + enum class RuntimeExecutionError { // 0 is reserved for success + NO_TRANSACTIONS_WERE_STARTED = 1, + EXPORT_FUNCTION_NOT_FOUND + }; +} // namespace kagome::runtime + +OUTCOME_HPP_DECLARE_ERROR(kagome::runtime, RuntimeExecutionError); diff --git a/core/runtime/common/runtime_instances_pool.cpp b/core/runtime/common/runtime_instances_pool.cpp index 2a47d024ad..0eeac1f2b1 100644 --- a/core/runtime/common/runtime_instances_pool.cpp +++ b/core/runtime/common/runtime_instances_pool.cpp @@ -6,6 +6,7 @@ #include "runtime/common/runtime_instances_pool.hpp" +#include "common/monadic_utils.hpp" #include "runtime/common/uncompress_code_if_needed.hpp" #include "runtime/instance_environment.hpp" #include "runtime/module.hpp" @@ -21,14 +22,12 @@ namespace kagome::runtime { class BorrowedInstance : public ModuleInstance { public: BorrowedInstance(std::weak_ptr pool, - const RuntimeInstancesPool::RootHash &state, + const common::Hash256 &hash, std::shared_ptr instance) - : pool_{std::move(pool)}, - state_{state}, - instance_{std::move(instance)} {} + : pool_{std::move(pool)}, hash_{hash}, instance_{std::move(instance)} {} ~BorrowedInstance() { if (auto pool = pool_.lock()) { - pool->release(state_, std::move(instance_)); + pool->release(hash_, std::move(instance_)); } } @@ -64,52 +63,91 @@ namespace kagome::runtime { private: std::weak_ptr pool_; - RuntimeInstancesPool::RootHash state_; + common::Hash256 hash_; // either trie hash or code hash std::shared_ptr instance_; }; - RuntimeInstancesPool::RuntimeInstancesPool() : pools_{MODULES_CACHE_SIZE} {} - RuntimeInstancesPool::RuntimeInstancesPool( std::shared_ptr module_factory, size_t capacity) : module_factory_{std::move(module_factory)}, pools_{capacity} {} - outcome::result> - RuntimeInstancesPool::instantiate(const RootHash &code_hash, - common::BufferView code_zstd) { - std::unique_lock lock{mt_}; - auto entry = pools_.get(code_hash); - if (not entry) { + outcome::result, CompilationError> + RuntimeInstancesPool::instantiateFromCode(const CodeHash &code_hash, + common::BufferView code_zstd) { + std::unique_lock lock{pools_mtx_}; + auto pool_opt = pools_.get(code_hash); + + if (!pool_opt) { lock.unlock(); - common::Buffer code; - OUTCOME_TRY(uncompressCodeIfNeeded(code_zstd, code)); - OUTCOME_TRY(module, module_factory_->make(code)); - lock.lock(); - entry = pools_.get(code_hash); - if (not entry) { - entry = pools_.put(code_hash, {std::move(module), {}}); + if (auto future = getFutureCompiledModule(code_hash)) { + lock.lock(); + pool_opt = pools_.get(code_hash); + } else { + OUTCOME_TRY(module, tryCompileModule(code_hash, code_zstd)); + BOOST_ASSERT(module != nullptr); + lock.lock(); + pool_opt = std::ref(pools_.put(code_hash, InstancePool{module, {}})); } } - OUTCOME_TRY(instance, entry->get().instantiate(lock)); + auto instance = pool_opt->get().instantiate(lock); return std::make_shared( weak_from_this(), code_hash, std::move(instance)); } + std::optional> + RuntimeInstancesPool::getFutureCompiledModule( + const CodeHash &code_hash) const { + std::unique_lock l{compiling_modules_mtx_}; + auto iter = compiling_modules_.find(code_hash); + if (iter == compiling_modules_.end()) { + return std::nullopt; + } + auto future = iter->second; + l.unlock(); + return future; + } + + RuntimeInstancesPool::CompilationResult + RuntimeInstancesPool::tryCompileModule(const CodeHash &code_hash, + common::BufferView code_zstd) { + std::unique_lock l{compiling_modules_mtx_}; + std::promise promise; + auto [iter, inserted] = + compiling_modules_.insert({code_hash, promise.get_future()}); + BOOST_ASSERT(inserted); + l.unlock(); + + common::Buffer code; + CompilationResult res{nullptr}; + if (!uncompressCodeIfNeeded(code_zstd, code)) { + res = CompilationError{"Failed to uncompress code"}; + } else { + res = common::map_result(module_factory_->make(code), [](auto &&module) { + return std::shared_ptr(module); + }); + } + promise.set_value(res); + + l.lock(); + compiling_modules_.erase(iter); + return res; + } + outcome::result> - RuntimeInstancesPool::tryAcquire( - const RuntimeInstancesPool::RootHash &state) { - std::unique_lock lock{mt_}; + RuntimeInstancesPool::instantiateFromState( + const RuntimeInstancesPool::TrieHash &state) { + std::unique_lock lock{pools_mtx_}; auto entry = pools_.get(state); BOOST_ASSERT(entry); - OUTCOME_TRY(instance, entry->get().instantiate(lock)); + auto instance = entry->get().instantiate(lock); return std::make_shared( weak_from_this(), state, std::move(instance)); } void RuntimeInstancesPool::release( - const RuntimeInstancesPool::RootHash &state, + const RuntimeInstancesPool::TrieHash &state, std::shared_ptr &&instance) { - std::lock_guard guard{mt_}; + std::unique_lock guard{pools_mtx_}; auto entry = pools_.get(state); if (not entry) { entry = pools_.put(state, {instance->getModule(), {}}); @@ -118,8 +156,8 @@ namespace kagome::runtime { } std::optional> RuntimeInstancesPool::getModule( - const RuntimeInstancesPool::RootHash &state) { - std::lock_guard guard{mt_}; + const RuntimeInstancesPool::TrieHash &state) { + std::unique_lock guard{pools_mtx_}; if (auto entry = pools_.get(state)) { return entry->get().module; } @@ -127,16 +165,17 @@ namespace kagome::runtime { } void RuntimeInstancesPool::putModule( - const RuntimeInstancesPool::RootHash &state, + const RuntimeInstancesPool::TrieHash &state, std::shared_ptr module) { - std::lock_guard guard{mt_}; + std::unique_lock guard{pools_mtx_}; if (not pools_.get(state)) { pools_.put(state, {std::move(module), {}}); } } - outcome::result> - RuntimeInstancesPool::Entry::instantiate(std::unique_lock &lock) { + std::shared_ptr + RuntimeInstancesPool::InstancePool::instantiate( + std::unique_lock &lock) { if (instances.empty()) { auto copy = module; lock.unlock(); diff --git a/core/runtime/common/runtime_instances_pool.hpp b/core/runtime/common/runtime_instances_pool.hpp index 84dc970598..6e1a2077e4 100644 --- a/core/runtime/common/runtime_instances_pool.hpp +++ b/core/runtime/common/runtime_instances_pool.hpp @@ -8,13 +8,15 @@ #include "runtime/module_repository.hpp" +#include #include -#include +#include +#include +#include "runtime/module_factory.hpp" #include "utils/lru.hpp" namespace kagome::runtime { - class ModuleFactory; /** * @brief Pool of runtime instances - per state. Encapsulates modules cache. @@ -22,18 +24,18 @@ namespace kagome::runtime { */ class RuntimeInstancesPool final : public std::enable_shared_from_this { - using ModuleInstancePool = std::stack>; + static constexpr size_t DEFAULT_MODULES_CACHE_SIZE = 2; public: - using RootHash = storage::trie::RootHash; - - RuntimeInstancesPool(); + using TrieHash = storage::trie::RootHash; + using CodeHash = storage::trie::RootHash; RuntimeInstancesPool(std::shared_ptr module_factory, - size_t capacity); + size_t capacity = DEFAULT_MODULES_CACHE_SIZE); - outcome::result> instantiate( - const RootHash &code_hash, common::BufferView code_zstd); + outcome::result, CompilationError> + instantiateFromCode(const CodeHash &code_hash, + common::BufferView code_zstd); /** * @brief Instantiate new or reuse existing ModuleInstance for the provided @@ -44,8 +46,8 @@ namespace kagome::runtime { * @return pointer to the acquired ModuleInstance if success. Error * otherwise. */ - outcome::result> tryAcquire( - const RootHash &state); + outcome::result> instantiateFromState( + const TrieHash &state); /** * @brief Releases the module instance (returns it to the pool) * @@ -53,7 +55,7 @@ namespace kagome::runtime { * module code we are releasing an instance of. * @param instance - instance to be released. */ - void release(const RootHash &state, + void release(const TrieHash &state, std::shared_ptr &&instance); /** @@ -63,30 +65,42 @@ namespace kagome::runtime { * @return Module if any, nullopt otherwise */ std::optional> getModule( - const RootHash &state); + const TrieHash &state); /** * @brief Puts new module into internal cache * - * @param state - runtime block, by its root hash + * @param state - storage hash of the block containing the code of the + * module * @param module - new module pointer */ - void putModule(const RootHash &state, std::shared_ptr module); + void putModule(const TrieHash &state, std::shared_ptr module); private: - struct Entry { + struct InstancePool { std::shared_ptr module; std::vector> instances; - outcome::result> instantiate( + std::shared_ptr instantiate( std::unique_lock &lock); }; + using CompilationResult = + outcome::result, CompilationError>; + CompilationResult tryCompileModule(const CodeHash &code_hash, + common::BufferView code_zstd); + + std::optional> getFutureCompiledModule( + const CodeHash &code_hash) const; + std::shared_ptr module_factory_; - std::mutex mt_; - static constexpr size_t MODULES_CACHE_SIZE = 2; - Lru pools_; + std::mutex pools_mtx_; + Lru pools_; + + mutable std::mutex compiling_modules_mtx_; + std::unordered_map> + compiling_modules_; }; } // namespace kagome::runtime diff --git a/core/runtime/common/runtime_transaction_error.cpp b/core/runtime/common/runtime_transaction_error.cpp deleted file mode 100644 index a8dac28cb9..0000000000 --- a/core/runtime/common/runtime_transaction_error.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright Quadrivium LLC - * All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "runtime/common/runtime_transaction_error.hpp" - -OUTCOME_CPP_DEFINE_CATEGORY(kagome::runtime, RuntimeTransactionError, e) { - using E = kagome::runtime::RuntimeTransactionError; - switch (e) { - case E::NO_TRANSACTIONS_WERE_STARTED: - return "no transactions were started"; - case E::EXPORT_FUNCTION_NOT_FOUND: - return "Export function not found"; - } - return "unknown TransactionError"; -} diff --git a/core/runtime/common/runtime_transaction_error.hpp b/core/runtime/common/runtime_transaction_error.hpp deleted file mode 100644 index 54c6fee41c..0000000000 --- a/core/runtime/common/runtime_transaction_error.hpp +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright Quadrivium LLC - * All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include - -namespace kagome::runtime { - - /** - * @brief RuntimeTransactionError enum provides error codes for storage - * transactions mechanism - */ - enum class RuntimeTransactionError { // 0 is reserved for success - NO_TRANSACTIONS_WERE_STARTED = 1, - EXPORT_FUNCTION_NOT_FOUND, - }; -} // namespace kagome::runtime - -OUTCOME_HPP_DECLARE_ERROR(kagome::runtime, RuntimeTransactionError); diff --git a/core/runtime/common/trie_storage_provider_impl.cpp b/core/runtime/common/trie_storage_provider_impl.cpp index cd3415e526..1d95c0a2b4 100644 --- a/core/runtime/common/trie_storage_provider_impl.cpp +++ b/core/runtime/common/trie_storage_provider_impl.cpp @@ -6,7 +6,7 @@ #include "runtime/common/trie_storage_provider_impl.hpp" -#include "runtime/common/runtime_transaction_error.hpp" +#include "runtime/common/runtime_execution_error.hpp" #include "storage/trie/impl/topper_trie_batch_impl.hpp" #include "storage/trie/trie_batches.hpp" @@ -177,7 +177,7 @@ namespace kagome::runtime { outcome::result TrieStorageProviderImpl::rollbackTransaction() { if (transaction_stack_.empty()) { - return RuntimeTransactionError::NO_TRANSACTIONS_WERE_STARTED; + return RuntimeExecutionError::NO_TRANSACTIONS_WERE_STARTED; } SL_TRACE(logger_, @@ -189,7 +189,7 @@ namespace kagome::runtime { outcome::result TrieStorageProviderImpl::commitTransaction() { if (transaction_stack_.empty()) { - return RuntimeTransactionError::NO_TRANSACTIONS_WERE_STARTED; + return RuntimeExecutionError::NO_TRANSACTIONS_WERE_STARTED; } OUTCOME_TRY(transaction_stack_.back().main_batch->writeBack()); diff --git a/core/runtime/common/trie_storage_provider_impl.hpp b/core/runtime/common/trie_storage_provider_impl.hpp index f20713bb76..a7a7f89edb 100644 --- a/core/runtime/common/trie_storage_provider_impl.hpp +++ b/core/runtime/common/trie_storage_provider_impl.hpp @@ -13,7 +13,7 @@ #include "common/buffer.hpp" #include "log/logger.hpp" -#include "runtime/common/runtime_transaction_error.hpp" +#include "runtime/common/runtime_execution_error.hpp" #include "storage/trie/serialization/trie_serializer.hpp" #include "storage/trie/trie_storage.hpp" diff --git a/core/runtime/common/uncompress_code_if_needed.cpp b/core/runtime/common/uncompress_code_if_needed.cpp index c3c2e8b0bd..d9baa53a46 100644 --- a/core/runtime/common/uncompress_code_if_needed.cpp +++ b/core/runtime/common/uncompress_code_if_needed.cpp @@ -31,7 +31,7 @@ namespace kagome::runtime { // https://github.com/paritytech/substrate/blob/polkadot-v0.9.8/primitives/maybe-compressed-blob/src/lib.rs#L35 constexpr size_t kCodeBlobBombLimit = 50 * 1024 * 1024; - outcome::result uncompressCodeIfNeeded(common::BufferView buf, + outcome::result uncompressCodeIfNeeded(common::BufferView buf, common::Buffer &res) { if (startsWith(buf, kZstdPrefix)) { auto zstd = buf.subspan(std::size(kZstdPrefix)); diff --git a/core/runtime/common/uncompress_code_if_needed.hpp b/core/runtime/common/uncompress_code_if_needed.hpp index 6c1666dd1a..ef88c757ef 100644 --- a/core/runtime/common/uncompress_code_if_needed.hpp +++ b/core/runtime/common/uncompress_code_if_needed.hpp @@ -14,8 +14,14 @@ namespace kagome::runtime { BOMB_SIZE_REACHED, }; - outcome::result uncompressCodeIfNeeded(common::BufferView buf, - common::Buffer &res); + outcome::result uncompressCodeIfNeeded( + common::BufferView buf, common::Buffer &res); } // namespace kagome::runtime OUTCOME_HPP_DECLARE_ERROR(kagome::runtime, UncompressError); + +namespace kagome::runtime { + inline auto format_as(UncompressError e) { + return make_error_code(e); + } +} diff --git a/core/runtime/module.hpp b/core/runtime/module.hpp index 77cc6ec9bb..10444a8467 100644 --- a/core/runtime/module.hpp +++ b/core/runtime/module.hpp @@ -25,8 +25,7 @@ namespace kagome::runtime { public: virtual ~Module() = default; - virtual outcome::result> instantiate() - const = 0; + virtual std::shared_ptr instantiate() const = 0; }; /** diff --git a/core/runtime/module_factory.hpp b/core/runtime/module_factory.hpp index 5425d36378..dc920ff413 100644 --- a/core/runtime/module_factory.hpp +++ b/core/runtime/module_factory.hpp @@ -10,17 +10,35 @@ #include "outcome/outcome.hpp" #include "runtime/instance_environment.hpp" +#include "runtime/types.hpp" #include "storage/trie/types.hpp" namespace kagome::runtime { class Module; + struct CompilationError : std::runtime_error { + CompilationError(const std::string& message) + : std::runtime_error(message.c_str()) {} + + std::string_view message() const { + return what(); + } + }; + + inline std::error_code make_error_code(CompilationError) { + return Error::COMPILATION_FAILED; + } + + inline void outcome_throw_as_system_error_with_payload(CompilationError e) { + throw e; + } + class ModuleFactory { public: virtual ~ModuleFactory() = default; - virtual outcome::result> make( + virtual outcome::result, CompilationError> make( common::BufferView code) const = 0; }; diff --git a/core/runtime/module_repository.hpp b/core/runtime/module_repository.hpp index e76fb5b8b2..1c6c1f050c 100644 --- a/core/runtime/module_repository.hpp +++ b/core/runtime/module_repository.hpp @@ -28,6 +28,7 @@ namespace kagome::runtime { */ class ModuleRepository { public: + virtual ~ModuleRepository() = default; /** diff --git a/core/runtime/runtime_api/impl/beefy.cpp b/core/runtime/runtime_api/impl/beefy.cpp index f50b415d6c..6c10c61bc2 100644 --- a/core/runtime/runtime_api/impl/beefy.cpp +++ b/core/runtime/runtime_api/impl/beefy.cpp @@ -6,7 +6,7 @@ #include "runtime/runtime_api/impl/beefy.hpp" -#include "runtime/common/runtime_transaction_error.hpp" +#include "runtime/common/runtime_execution_error.hpp" #include "runtime/executor.hpp" namespace kagome::runtime { @@ -22,7 +22,7 @@ namespace kagome::runtime { if (r) { return std::move(r.value()); } - if (r.error() == RuntimeTransactionError::EXPORT_FUNCTION_NOT_FOUND) { + if (r.error() == RuntimeExecutionError::EXPORT_FUNCTION_NOT_FOUND) { return std::nullopt; } return r.error(); diff --git a/core/runtime/runtime_api/impl/parachain_host.cpp b/core/runtime/runtime_api/impl/parachain_host.cpp index 5f7ea6979a..53d4ae8a10 100644 --- a/core/runtime/runtime_api/impl/parachain_host.cpp +++ b/core/runtime/runtime_api/impl/parachain_host.cpp @@ -9,6 +9,7 @@ #include "common/blob.hpp" #include "runtime/executor.hpp" #include "runtime/runtime_api/impl/parachain_host_types_serde.hpp" +#include "scale/std_variant.hpp" namespace kagome::runtime { diff --git a/core/runtime/runtime_api/parachain_host_types.hpp b/core/runtime/runtime_api/parachain_host_types.hpp index b9f244d3bc..a70695a9f9 100644 --- a/core/runtime/runtime_api/parachain_host_types.hpp +++ b/core/runtime/runtime_api/parachain_host_types.hpp @@ -121,11 +121,13 @@ namespace kagome::runtime { } }; + using FreeCore = Empty; + using ValidatorGroupsAndDescriptor = std::tuple, GroupDescriptor>; - using CoreState = boost::variant>; // 2 + using CoreState = std::variant; // 2 enum class OccupiedCoreAssumption : uint8_t { Included, // 0 TimedOut, // 1 diff --git a/core/runtime/types.hpp b/core/runtime/types.hpp index 32f636ca6d..6aac0d259d 100644 --- a/core/runtime/types.hpp +++ b/core/runtime/types.hpp @@ -10,6 +10,8 @@ #include #include +#include "outcome/outcome.hpp" + namespace kagome::runtime { /** * @brief type of wasm log levels @@ -71,4 +73,11 @@ namespace kagome::runtime { return {minor_part, major_part}; } + + enum class Error { + COMPILATION_FAILED = 1, + }; + } // namespace kagome::runtime + +OUTCOME_HPP_DECLARE_ERROR(kagome::runtime, Error); diff --git a/core/runtime/wavm/core_api_factory_impl.cpp b/core/runtime/wavm/core_api_factory_impl.cpp index 2b34fd8260..86c75d906e 100644 --- a/core/runtime/wavm/core_api_factory_impl.cpp +++ b/core/runtime/wavm/core_api_factory_impl.cpp @@ -50,14 +50,17 @@ namespace kagome::runtime::wavm { const primitives::BlockInfo &, const storage::trie::RootHash &) override { if (instance_ == nullptr) { - auto module = ModuleImpl::compileFrom(compartment_, - *module_params_, - intrinsic_module_, - instance_env_factory_, - code_, - code_hash_); - OUTCOME_TRY(inst, module->instantiate()); - last_compiled_module_->set(std::move(module)); + auto module_res = ModuleImpl::compileFrom(compartment_, + *module_params_, + intrinsic_module_, + instance_env_factory_, + code_, + code_hash_); + if (!module_res) { + return make_error_code(module_res.error()); + } + auto inst = module_res.value()->instantiate(); + last_compiled_module_->set(std::move(module_res.value())); instance_ = std::move(inst); } return instance_; diff --git a/core/runtime/wavm/module.cpp b/core/runtime/wavm/module.cpp index 1f3dda225c..0345dd0e42 100644 --- a/core/runtime/wavm/module.cpp +++ b/core/runtime/wavm/module.cpp @@ -21,7 +21,8 @@ namespace kagome::runtime::wavm { - std::shared_ptr ModuleImpl::compileFrom( + outcome::result, CompilationError> + ModuleImpl::compileFrom( std::shared_ptr compartment, ModuleParams &module_params, std::shared_ptr intrinsic_module, @@ -35,13 +36,13 @@ namespace kagome::runtime::wavm { featureSpec.extendedNameSection = true; log::Logger logger = log::createLogger("WAVM Module", "wavm"); logger->info( - "Compiling WebAssembly module for Runtime (going to take a few dozens " - "of seconds)"); + "Compiling WebAssembly module with code hash {} (going to " + "take a few dozens of seconds)", + code_hash); if (!WAVM::Runtime::loadBinaryModule( code.data(), code.size(), module, featureSpec, &loadError)) { - logger->critical("Error loading WAVM binary module: {}", - loadError.message); - return nullptr; + logger->warn("Error loading WAVM binary module: {}", loadError.message); + return CompilationError{std::move(loadError.message)}; } auto &imports = WAVM::Runtime::getModuleIR(module).memories.imports; @@ -77,8 +78,7 @@ namespace kagome::runtime::wavm { BOOST_ASSERT(module_); } - outcome::result> ModuleImpl::instantiate() - const { + std::shared_ptr ModuleImpl::instantiate() const { #if defined(__GNUC__) and not defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdangling-reference" diff --git a/core/runtime/wavm/module.hpp b/core/runtime/wavm/module.hpp index 70c172d3a3..0b3aa78f7d 100644 --- a/core/runtime/wavm/module.hpp +++ b/core/runtime/wavm/module.hpp @@ -12,6 +12,7 @@ #include "common/blob.hpp" #include "log/logger.hpp" +#include "runtime/module_factory.hpp" namespace WAVM::Runtime { struct Compartment; @@ -31,15 +32,15 @@ namespace kagome::runtime::wavm { class ModuleImpl final : public runtime::Module, public std::enable_shared_from_this { public: - static std::shared_ptr compileFrom( - std::shared_ptr compartment, - ModuleParams &module_params, - std::shared_ptr intrinsic_module, - std::shared_ptr env_factory, - common::BufferView code, - const common::Hash256 &code_hash); + static outcome::result, CompilationError> + compileFrom(std::shared_ptr compartment, + ModuleParams &module_params, + std::shared_ptr intrinsic_module, + std::shared_ptr env_factory, + common::BufferView code, + const common::Hash256 &code_hash); - outcome::result> instantiate() + std::shared_ptr instantiate() const override; ModuleImpl(std::shared_ptr compartment, diff --git a/core/runtime/wavm/module_factory_impl.cpp b/core/runtime/wavm/module_factory_impl.cpp index af8ab6350d..e5de5d5003 100644 --- a/core/runtime/wavm/module_factory_impl.cpp +++ b/core/runtime/wavm/module_factory_impl.cpp @@ -36,14 +36,16 @@ namespace kagome::runtime::wavm { } } - outcome::result> ModuleFactoryImpl::make( - common::BufferView code) const { - return ModuleImpl::compileFrom(compartment_, - *module_params_, - intrinsic_module_, - env_factory_, - code, - hasher_->sha2_256(code)); + outcome::result, CompilationError> + ModuleFactoryImpl::make(common::BufferView code) const { + OUTCOME_TRY(module, + ModuleImpl::compileFrom(compartment_, + *module_params_, + intrinsic_module_, + env_factory_, + code, + hasher_->sha2_256(code))); + return module; } } // namespace kagome::runtime::wavm diff --git a/core/runtime/wavm/module_factory_impl.hpp b/core/runtime/wavm/module_factory_impl.hpp index 04934d0f2b..58b4681846 100644 --- a/core/runtime/wavm/module_factory_impl.hpp +++ b/core/runtime/wavm/module_factory_impl.hpp @@ -35,7 +35,7 @@ namespace kagome::runtime::wavm { std::optional> module_cache, std::shared_ptr hasher); - outcome::result> make( + outcome::result, CompilationError> make( common::BufferView code) const override; private: diff --git a/core/runtime/wavm/module_instance.cpp b/core/runtime/wavm/module_instance.cpp index 2e7c760ab0..b4fcecface 100644 --- a/core/runtime/wavm/module_instance.cpp +++ b/core/runtime/wavm/module_instance.cpp @@ -13,7 +13,7 @@ #include "host_api/host_api.hpp" #include "log/profiling_logger.hpp" -#include "runtime/common/runtime_transaction_error.hpp" +#include "runtime/common/runtime_execution_error.hpp" #include "runtime/memory_provider.hpp" #include "runtime/module_repository.hpp" #include "runtime/trie_storage_provider.hpp" @@ -119,7 +119,7 @@ namespace kagome::runtime::wavm { WAVM::Runtime::getInstanceExport(instance_, name.data())); if (!function) { SL_DEBUG(logger_, "The requested function {} not found", name); - return RuntimeTransactionError::EXPORT_FUNCTION_NOT_FOUND; + return RuntimeExecutionError::EXPORT_FUNCTION_NOT_FOUND; } const WAVM::IR::FunctionType functionType = WAVM::Runtime::getFunctionType(function); diff --git a/core/scale/CMakeLists.txt b/core/scale/CMakeLists.txt index 528e18e2bb..8a87402a4f 100644 --- a/core/scale/CMakeLists.txt +++ b/core/scale/CMakeLists.txt @@ -12,3 +12,5 @@ target_link_libraries(scale_libp2p_types scale::scale p2p::p2p ) + +kagome_install(scale_libp2p_types) diff --git a/core/scale/std_variant.hpp b/core/scale/std_variant.hpp new file mode 100644 index 0000000000..bea6949ea7 --- /dev/null +++ b/core/scale/std_variant.hpp @@ -0,0 +1,43 @@ +/** + * Copyright Quadrivium LLC All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include + +namespace scale { + + template + requires Stream::is_encoder_stream + Stream &operator<<(Stream &stream, const std::variant &variant) { + stream << static_cast(variant.index()); + std::visit([&stream](const auto &v) { stream << v; }, variant); + return stream; + } + + template + requires Stream::is_decoder_stream + constexpr auto make_decoder() { + return [](Stream &stream, std::variant &variant) { + variant.template emplace