diff --git a/core/benchmark/block_execution_benchmark.cpp b/core/benchmark/block_execution_benchmark.cpp index c4f9e775c5..2b159a6dd2 100644 --- a/core/benchmark/block_execution_benchmark.cpp +++ b/core/benchmark/block_execution_benchmark.cpp @@ -123,18 +123,15 @@ namespace kagome::benchmark { std::shared_ptr core_api, std::shared_ptr block_tree, std::shared_ptr module_repo, - std::shared_ptr code_provider, std::shared_ptr trie_storage) : logger_{log::createLogger("BlockExecutionBenchmark", "benchmark")}, core_api_{core_api}, block_tree_{block_tree}, module_repo_{module_repo}, - code_provider_{code_provider}, trie_storage_{trie_storage} { BOOST_ASSERT(block_tree_ != nullptr); BOOST_ASSERT(core_api_ != nullptr); BOOST_ASSERT(module_repo_ != nullptr); - BOOST_ASSERT(code_provider_ != nullptr); BOOST_ASSERT(trie_storage_ != nullptr); } diff --git a/core/benchmark/block_execution_benchmark.hpp b/core/benchmark/block_execution_benchmark.hpp index c5de1afb47..33290722c0 100644 --- a/core/benchmark/block_execution_benchmark.hpp +++ b/core/benchmark/block_execution_benchmark.hpp @@ -19,7 +19,6 @@ namespace kagome::blockchain { namespace kagome::runtime { class Core; class ModuleRepository; - class RuntimeCodeProvider; } // namespace kagome::runtime namespace kagome::storage::trie { @@ -45,7 +44,6 @@ namespace kagome::benchmark { std::shared_ptr core_api, std::shared_ptr block_tree, std::shared_ptr module_repo, - std::shared_ptr code_provider, std::shared_ptr trie_storage); outcome::result run(Config config); @@ -55,7 +53,6 @@ namespace kagome::benchmark { std::shared_ptr core_api_; std::shared_ptr block_tree_; std::shared_ptr module_repo_; - std::shared_ptr code_provider_; std::shared_ptr trie_storage_; }; diff --git a/core/host_api/impl/CMakeLists.txt b/core/host_api/impl/CMakeLists.txt index 2bb58ebe0f..10f0901714 100644 --- a/core/host_api/impl/CMakeLists.txt +++ b/core/host_api/impl/CMakeLists.txt @@ -51,7 +51,6 @@ add_library(misc_extension target_link_libraries(misc_extension scale::scale logger - constant_code_provider outcome blob ) diff --git a/core/injector/application_injector.cpp b/core/injector/application_injector.cpp index 8df1fc9916..881cced0b3 100644 --- a/core/injector/application_injector.cpp +++ b/core/injector/application_injector.cpp @@ -182,6 +182,7 @@ #include "runtime/runtime_api/impl/session_keys_api.hpp" #include "runtime/runtime_api/impl/tagged_transaction_queue.hpp" #include "runtime/runtime_api/impl/transaction_payment_api.hpp" +#include "runtime/wabt/instrument.hpp" #if KAGOME_WASM_COMPILER_WASM_EDGE == 1 @@ -405,7 +406,6 @@ namespace { injector.template create>(), injector.template create>(), injector.template create>(), - injector.template create>(), module_cache_opt, injector.template create>()); }), @@ -561,7 +561,6 @@ namespace { di::bind.template to(), di::bind.template to(), di::bind.template to(), - di::bind.template to(), di::bind.template to(), std::forward(args)...); } diff --git a/core/parachain/pvf/kagome_pvf_worker_injector.hpp b/core/parachain/pvf/kagome_pvf_worker_injector.hpp index 323bd090e0..9b538193d2 100644 --- a/core/parachain/pvf/kagome_pvf_worker_injector.hpp +++ b/core/parachain/pvf/kagome_pvf_worker_injector.hpp @@ -107,7 +107,6 @@ namespace kagome::parachain { injector.template create>(), injector.template create>(), injector.template create>(), - injector.template create>(), module_cache_opt, injector.template create>()); }), diff --git a/core/parachain/pvf/session_params.hpp b/core/parachain/pvf/session_params.hpp index 99fe78590b..94281ac572 100644 --- a/core/parachain/pvf/session_params.hpp +++ b/core/parachain/pvf/session_params.hpp @@ -12,18 +12,24 @@ namespace kagome::parachain { inline outcome::result sessionParams( runtime::ParachainHost &api, const primitives::BlockHash &relay_parent) { + // https://github.com/paritytech/polkadot-sdk/blob/e0c081dbd46c1e6edca1ce2c62298f5f3622afdd/polkadot/node/core/pvf/common/src/executor_interface.rs#L46-L47 + constexpr uint32_t kDefaultHeapPagesEstimate = 32; + constexpr uint32_t kExtraHeapPages = 2048; OUTCOME_TRY(session_index, api.session_index_for_child(relay_parent)); OUTCOME_TRY(session_params, api.session_executor_params(relay_parent, session_index)); runtime::RuntimeContext::ContextParams config; config.memory_limits.max_stack_values_num = runtime::RuntimeContext::DEFAULT_STACK_MAX; + config.memory_limits.heap_alloc_strategy = + HeapAllocStrategyDynamic{kDefaultHeapPagesEstimate + kExtraHeapPages}; if (session_params) { for (auto ¶m : *session_params) { if (auto *stack_max = get_if(¶m)) { config.memory_limits.max_stack_values_num = stack_max->max_values_num; } else if (auto *pages_max = get_if(¶m)) { - config.memory_limits.max_memory_pages_num = pages_max->limit; + config.memory_limits.heap_alloc_strategy = HeapAllocStrategyDynamic{ + kDefaultHeapPagesEstimate + pages_max->limit}; } } } diff --git a/core/runtime/CMakeLists.txt b/core/runtime/CMakeLists.txt index 1d16cc1192..410617169a 100644 --- a/core/runtime/CMakeLists.txt +++ b/core/runtime/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(common) add_subdirectory(binaryen) +add_subdirectory(wabt) add_library(wasm_compiler INTERFACE) diff --git a/core/runtime/binaryen/memory_impl.cpp b/core/runtime/binaryen/memory_impl.cpp index 824b0b4f0d..06f8cdd05a 100644 --- a/core/runtime/binaryen/memory_impl.cpp +++ b/core/runtime/binaryen/memory_impl.cpp @@ -23,6 +23,10 @@ namespace kagome::runtime::binaryen { memory_->resize(kInitialMemorySize); } + std::optional MemoryImpl::pagesMax() const { + return memory_->pagesMax(); + } + WasmPointer MemoryImpl::allocate(WasmSize size) { return allocator_->allocate(size); } diff --git a/core/runtime/binaryen/memory_impl.hpp b/core/runtime/binaryen/memory_impl.hpp index 40bb22dba7..2db2b0f9b9 100644 --- a/core/runtime/binaryen/memory_impl.hpp +++ b/core/runtime/binaryen/memory_impl.hpp @@ -64,6 +64,8 @@ namespace kagome::runtime::binaryen { return memory_->getSize(); } + std::optional pagesMax() const override; + outcome::result view(WasmPointer ptr, WasmSize size) const override; diff --git a/core/runtime/binaryen/runtime_external_interface.cpp b/core/runtime/binaryen/runtime_external_interface.cpp index 753e8717c9..f42f1121fd 100644 --- a/core/runtime/binaryen/runtime_external_interface.cpp +++ b/core/runtime/binaryen/runtime_external_interface.cpp @@ -196,6 +196,9 @@ namespace kagome::runtime::binaryen { void RuntimeExternalInterface::init(wasm::Module &wasm, wasm::ModuleInstance &instance) { memory.resize(wasm.memory.initial * wasm::Memory::kPageSize); + if (wasm.memory.hasMax()) { + memory.pages_max = wasm.memory.max; + } // apply memory segments for (auto &segment : wasm.memory.segments) { wasm::Address offset = diff --git a/core/runtime/binaryen/runtime_external_interface.hpp b/core/runtime/binaryen/runtime_external_interface.hpp index 529d3494f4..345d1e07e0 100644 --- a/core/runtime/binaryen/runtime_external_interface.hpp +++ b/core/runtime/binaryen/runtime_external_interface.hpp @@ -37,8 +37,11 @@ namespace kagome::runtime::binaryen { class RuntimeExternalInterface : public wasm::ModuleInstance::ExternalInterface { class Memory { + friend class RuntimeExternalInterface; + using Mem = std::vector; Mem memory; + std::optional pages_max; template static bool aligned(const char *address) { static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2"); @@ -66,6 +69,9 @@ namespace kagome::runtime::binaryen { auto getSize() const { return memory.size(); } + std::optional pagesMax() const { + return pages_max; + } template void set(size_t address, T value) { check(address, sizeof(T)); @@ -169,8 +175,7 @@ namespace kagome::runtime::binaryen { memory.resize(newSize); } - [[noreturn]] - void trap(const char *why) override { + [[noreturn]] void trap(const char *why) override { logger_->error("Trap: {}", why); throw wasm::TrapException{}; } diff --git a/core/runtime/common/CMakeLists.txt b/core/runtime/common/CMakeLists.txt index 269f75b8a4..34a13ebc69 100644 --- a/core/runtime/common/CMakeLists.txt +++ b/core/runtime/common/CMakeLists.txt @@ -5,14 +5,9 @@ # add_library(runtime_common - stack_limiter.cpp memory_error.cpp ) target_link_libraries(runtime_common - wabt::wabt - blob - logger - uncompress_if_needed outcome ) kagome_install(runtime_common) @@ -27,14 +22,6 @@ target_link_libraries(storage_code_provider ) kagome_install(storage_code_provider) -add_library(constant_code_provider - constant_code_provider.cpp - ) -target_link_libraries(constant_code_provider - uncompress_if_needed - ) -kagome_install(constant_code_provider) - add_library(uncompress_if_needed uncompress_code_if_needed.cpp ) @@ -76,6 +63,7 @@ add_library(module_repository target_link_libraries(module_repository outcome uncompress_if_needed + wasm_instrument blob executor runtime_common @@ -90,6 +78,7 @@ add_library(executor target_link_libraries(executor logger uncompress_if_needed + wasm_instrument storage mp_utils runtime_common diff --git a/core/runtime/common/constant_code_provider.cpp b/core/runtime/common/constant_code_provider.cpp deleted file mode 100644 index 9dece14d69..0000000000 --- a/core/runtime/common/constant_code_provider.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright Quadrivium LLC - * All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "runtime/common/constant_code_provider.hpp" - -namespace kagome::runtime { - - ConstantCodeProvider::ConstantCodeProvider(common::Buffer code) - : code_{std::move(code)} {} - - outcome::result ConstantCodeProvider::getCodeAt( - const storage::trie::RootHash &) const { - return code_; - } - -} // namespace kagome::runtime diff --git a/core/runtime/common/constant_code_provider.hpp b/core/runtime/common/constant_code_provider.hpp deleted file mode 100644 index 5b436eeff1..0000000000 --- a/core/runtime/common/constant_code_provider.hpp +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright Quadrivium LLC - * All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include "runtime/runtime_code_provider.hpp" - -namespace kagome::runtime { - - /** - * A code provider that serves only one fixed blob of code for any state - */ - class ConstantCodeProvider final : public RuntimeCodeProvider { - public: - explicit ConstantCodeProvider(common::Buffer code); - - outcome::result getCodeAt( - const storage::trie::RootHash &at) const override; - - private: - common::Buffer code_; - }; - -} // namespace kagome::runtime diff --git a/core/runtime/common/core_api_factory_impl.hpp b/core/runtime/common/core_api_factory_impl.hpp index c1f45d2f03..d89141bdea 100644 --- a/core/runtime/common/core_api_factory_impl.hpp +++ b/core/runtime/common/core_api_factory_impl.hpp @@ -20,7 +20,6 @@ namespace kagome::blockchain { namespace kagome::runtime { class ModuleFactory; - class SingleModuleCache; } // namespace kagome::runtime namespace kagome::runtime { @@ -29,7 +28,8 @@ namespace kagome::runtime { : public runtime::CoreApiFactory, public std::enable_shared_from_this { public: - explicit CoreApiFactoryImpl(std::shared_ptr module_factory); + explicit CoreApiFactoryImpl( + std::shared_ptr module_factory); ~CoreApiFactoryImpl() = default; outcome::result> make( diff --git a/core/runtime/common/memory_allocator.cpp b/core/runtime/common/memory_allocator.cpp index 0111ec56a6..c7449f77a4 100644 --- a/core/runtime/common/memory_allocator.cpp +++ b/core/runtime/common/memory_allocator.cpp @@ -33,8 +33,7 @@ namespace kagome::runtime { MemoryAllocator::MemoryAllocator(Memory &memory, const MemoryConfig &config) : memory_{memory}, offset_{roundUpAlign(config.heap_base)}, - max_memory_pages_num_{ - config.limits.max_memory_pages_num.value_or(kMaxPages)} { + max_memory_pages_num_{memory_.pagesMax().value_or(kMaxPages)} { BOOST_ASSERT(max_memory_pages_num_ > 0); } diff --git a/core/runtime/common/module_instance.cpp b/core/runtime/common/module_instance.cpp index 2e7a47ef5b..250f9606ff 100644 --- a/core/runtime/common/module_instance.cpp +++ b/core/runtime/common/module_instance.cpp @@ -43,29 +43,6 @@ namespace kagome::runtime { .resetMemory(MemoryConfig{heap_base, limits})); auto &memory = memory_provider->getCurrentMemory()->get(); - // TODO: https://github.com/qdrvm/kagome/issues/1962 limit max memory - if (auto &storage = getEnvironment().storage_provider) { - static const auto heappages_key = ":heappages"_buf; - auto batch = storage->getCurrentBatch(); - OUTCOME_TRY(heappages, batch->tryGet(heappages_key)); - if (heappages) { - if (sizeof(uint64_t) != heappages->size()) { - SL_ERROR(log, - "Unable to read :heappages value. Type size mismatch. " - "Required {} bytes, but {} available", - sizeof(uint64_t), - heappages->size()); - } else { - uint64_t pages = common::le_bytes_to_uint64(heappages->view()); - memory.resize(pages * kMemoryPageSize); - SL_TRACE(log, - "Creating wasm module with non-default :heappages value set " - "to {}", - pages); - } - } - } - size_t max_data_segment_end = 0; size_t segments_num = 0; forDataSegment([&](ModuleInstance::SegmentOffset offset, diff --git a/core/runtime/common/module_repository_impl.cpp b/core/runtime/common/module_repository_impl.cpp index b38cea94af..a42c26366e 100644 --- a/core/runtime/common/module_repository_impl.cpp +++ b/core/runtime/common/module_repository_impl.cpp @@ -8,12 +8,14 @@ #include "log/profiling_logger.hpp" #include "runtime/common/runtime_instances_pool.hpp" +#include "runtime/heap_alloc_strategy_heappages.hpp" #include "runtime/instance_environment.hpp" #include "runtime/module.hpp" #include "runtime/module_factory.hpp" #include "runtime/module_instance.hpp" #include "runtime/runtime_code_provider.hpp" #include "runtime/runtime_upgrade_tracker.hpp" +#include "storage/trie/trie_storage.hpp" namespace kagome::runtime { using kagome::primitives::ThreadNumber; @@ -21,20 +23,22 @@ namespace kagome::runtime { ModuleRepositoryImpl::ModuleRepositoryImpl( std::shared_ptr runtime_instances_pool, + std::shared_ptr hasher, std::shared_ptr runtime_upgrade_tracker, + std::shared_ptr trie_storage, std::shared_ptr module_factory, - std::shared_ptr last_compiled_module, std::shared_ptr code_provider) : runtime_instances_pool_{std::move(runtime_instances_pool)}, + hasher_{std::move(hasher)}, runtime_upgrade_tracker_{std::move(runtime_upgrade_tracker)}, + trie_storage_{std::move(trie_storage)}, module_factory_{std::move(module_factory)}, - last_compiled_module_{std::move(last_compiled_module)}, code_provider_{code_provider}, + cache_{4}, logger_{log::createLogger("Module Repository", "runtime")} { BOOST_ASSERT(runtime_instances_pool_); BOOST_ASSERT(runtime_upgrade_tracker_); BOOST_ASSERT(module_factory_); - BOOST_ASSERT(last_compiled_module_); } outcome::result> @@ -45,33 +49,34 @@ namespace kagome::runtime { OUTCOME_TRY(state, runtime_upgrade_tracker_->getLastCodeUpdateState(block)); KAGOME_PROFILE_END(code_retrieval) - KAGOME_PROFILE_START(module_retrieval) { - // Add compiled module if any - if (auto module = last_compiled_module_->try_extract(); - module.has_value()) { - runtime_instances_pool_->putModule(state, module.value()); - } - - // Compile new module if required - if (auto opt_module = runtime_instances_pool_->getModule(state); - !opt_module.has_value()) { - SL_DEBUG(logger_, "Runtime module cache miss for state {}", state); + KAGOME_PROFILE_START(module_retrieval) + constexpr uint32_t kDefaultHeapAllocPages = 2048; + MemoryLimits config; + config.heap_alloc_strategy = + HeapAllocStrategyStatic{kDefaultHeapAllocPages}; + Item item; + OUTCOME_TRY(SAFE_UNIQUE(cache_)->outcome::result { + if (auto r = cache_.get(state)) { + item = r->get(); + } else { auto code = code_provider_->getCodeAt(state); if (not code.has_value()) { code = code_provider_->getCodeAt(storage_state); } - if (not code.has_value()) { - return code.as_failure(); + BOOST_OUTCOME_TRY(item.code, std::move(code)); + item.hash = hasher_->blake2b_256(*item.code); + OUTCOME_TRY(batch, trie_storage_->getEphemeralBatchAt(storage_state)); + OUTCOME_TRY(heappages, heapAllocStrategyHeappages(*batch)); + if (heappages) { + item.config.heap_alloc_strategy = *heappages; } - OUTCOME_TRY(new_module, module_factory_->make(code.value())); - runtime_instances_pool_->putModule(state, std::move(new_module)); + cache_.put(state, item); } - } - - // Try to acquire an instance (instantiate if needed) + return outcome::success(); + }); OUTCOME_TRY(runtime_instance, - runtime_instances_pool_->instantiateFromState( - state, RuntimeContext::ContextParams{})); + runtime_instances_pool_->instantiateFromCode( + item.hash, *item.code, {config})); KAGOME_PROFILE_END(module_retrieval) return runtime_instance; diff --git a/core/runtime/common/module_repository_impl.hpp b/core/runtime/common/module_repository_impl.hpp index 70bf2fbd14..d663314e3a 100644 --- a/core/runtime/common/module_repository_impl.hpp +++ b/core/runtime/common/module_repository_impl.hpp @@ -13,20 +13,30 @@ #include "log/logger.hpp" #include "runtime/instance_environment.hpp" +#include "utils/lru.hpp" +#include "utils/safe_object.hpp" + +namespace kagome::crypto { + class Hasher; +} // namespace kagome::crypto + +namespace kagome::storage::trie { + class TrieStorage; +} // namespace kagome::storage::trie namespace kagome::runtime { class RuntimeUpgradeTracker; class ModuleFactory; - class SingleModuleCache; class RuntimeInstancesPool; class ModuleRepositoryImpl final : public ModuleRepository { public: ModuleRepositoryImpl( std::shared_ptr runtime_instances_pool, + std::shared_ptr hasher, std::shared_ptr runtime_upgrade_tracker, + std::shared_ptr trie_storage, std::shared_ptr module_factory, - std::shared_ptr last_compiled_module, std::shared_ptr code_provider); outcome::result> getInstanceAt( @@ -34,11 +44,18 @@ namespace kagome::runtime { const storage::trie::RootHash &state) override; private: + struct Item { + common::Hash256 hash; + std::shared_ptr code; + MemoryLimits config; + }; std::shared_ptr runtime_instances_pool_; + std::shared_ptr hasher_; std::shared_ptr runtime_upgrade_tracker_; + std::shared_ptr trie_storage_; std::shared_ptr module_factory_; - std::shared_ptr last_compiled_module_; std::shared_ptr code_provider_; + SafeObject> cache_; log::Logger logger_; }; diff --git a/core/runtime/common/runtime_context.cpp b/core/runtime/common/runtime_context.cpp index e68d6d74cc..187ccb077d 100644 --- a/core/runtime/common/runtime_context.cpp +++ b/core/runtime/common/runtime_context.cpp @@ -9,7 +9,6 @@ #include "runtime/runtime_context.hpp" #include "blockchain/block_header_repository.hpp" -#include "runtime/common/stack_limiter.hpp" #include "runtime/common/uncompress_code_if_needed.hpp" #include "runtime/instance_environment.hpp" #include "runtime/memory_provider.hpp" @@ -18,6 +17,7 @@ #include "runtime/module_instance.hpp" #include "runtime/module_repository.hpp" #include "runtime/trie_storage_provider.hpp" +#include "runtime/wabt/instrument.hpp" #include "storage/trie/polkadot_trie/trie_error.hpp" OUTCOME_CPP_DEFINE_CATEGORY(kagome::runtime, Error, e) { @@ -54,17 +54,8 @@ namespace kagome::runtime { ContextParams params) { common::Buffer code; OUTCOME_TRY(runtime::uncompressCodeIfNeeded(code_zstd, code)); - if (params.memory_limits.max_stack_values_num) { - auto res = instrumentWithStackLimiter( - code, *params.memory_limits.max_stack_values_num); - if (!res) { - log::createLogger("RuntimeContextFactory", "runtime") - ->error("Failed to instrument wasm code with stack limiter: {}", - res.error().message()); - return Error::INSTRUMENTATION_FAILED; - } - code = std::move(res.value()); - } + BOOST_OUTCOME_TRY(code, + prepareBlobForCompilation(code, params.memory_limits)); auto runtime_module_res = module_factory.make(code); if (!runtime_module_res) { return Error::COMPILATION_FAILED; diff --git a/core/runtime/common/runtime_instances_pool.cpp b/core/runtime/common/runtime_instances_pool.cpp index d28b25c597..ef2f9e31ed 100644 --- a/core/runtime/common/runtime_instances_pool.cpp +++ b/core/runtime/common/runtime_instances_pool.cpp @@ -7,12 +7,12 @@ #include "runtime/common/runtime_instances_pool.hpp" #include "common/monadic_utils.hpp" -#include "runtime/common/stack_limiter.hpp" #include "runtime/common/uncompress_code_if_needed.hpp" #include "runtime/instance_environment.hpp" #include "runtime/module.hpp" #include "runtime/module_factory.hpp" #include "runtime/module_instance.hpp" +#include "runtime/wabt/instrument.hpp" namespace kagome::runtime { /** @@ -24,11 +24,15 @@ namespace kagome::runtime { public: BorrowedInstance(std::weak_ptr pool, const common::Hash256 &hash, + const RuntimeContext::ContextParams &config, std::shared_ptr instance) - : pool_{std::move(pool)}, hash_{hash}, instance_{std::move(instance)} {} + : pool_{std::move(pool)}, + hash_{hash}, + config_{config}, + instance_{std::move(instance)} {} ~BorrowedInstance() { if (auto pool = pool_.lock()) { - pool->release(hash_, std::move(instance_)); + pool->release(hash_, config_, std::move(instance_)); } } @@ -66,7 +70,8 @@ namespace kagome::runtime { private: std::weak_ptr pool_; - common::Hash256 hash_; // either trie hash or code hash + common::Hash256 hash_; + RuntimeContext::ContextParams config_; std::shared_ptr instance_; }; @@ -86,22 +91,23 @@ namespace kagome::runtime { common::BufferView code_zstd, const RuntimeContext::ContextParams &config) { std::unique_lock lock{pools_mtx_}; - auto pool_opt = pools_.get(code_hash); + Key key{code_hash, config}; + auto pool_opt = pools_.get(key); if (!pool_opt) { lock.unlock(); OUTCOME_TRY(module, tryCompileModule(code_hash, code_zstd, config)); lock.lock(); - pool_opt = pools_.get(code_hash); + pool_opt = pools_.get(key); if (!pool_opt) { - pool_opt = std::ref(pools_.put(code_hash, InstancePool{module, {}})); + pool_opt = std::ref(pools_.put(key, InstancePool{module, {}})); } } BOOST_ASSERT(pool_opt); OUTCOME_TRY(instance, pool_opt->get().instantiate(lock)); BOOST_ASSERT(shared_from_this()); return std::make_shared( - weak_from_this(), code_hash, std::move(instance)); + weak_from_this(), code_hash, config, std::move(instance)); } RuntimeInstancesPoolImpl::CompilationResult @@ -110,7 +116,8 @@ namespace kagome::runtime { common::BufferView code_zstd, const RuntimeContext::ContextParams &config) { std::unique_lock l{compiling_modules_mtx_}; - if (auto iter = compiling_modules_.find(code_hash); + Key key{code_hash, config}; + if (auto iter = compiling_modules_.find(key); iter != compiling_modules_.end()) { std::shared_future future = iter->second; l.unlock(); @@ -118,7 +125,7 @@ namespace kagome::runtime { } std::promise promise; auto [iter, is_inserted] = - compiling_modules_.insert({code_hash, promise.get_future()}); + compiling_modules_.insert({key, promise.get_future()}); BOOST_ASSERT(is_inserted); BOOST_ASSERT(iter != compiling_modules_.end()); l.unlock(); @@ -148,49 +155,19 @@ namespace kagome::runtime { return *res; } - outcome::result> - RuntimeInstancesPoolImpl::instantiateFromState( - const RuntimeInstancesPoolImpl::TrieHash &state, - const RuntimeContext::ContextParams &config) { - std::unique_lock lock{pools_mtx_}; - auto entry = pools_.get(state); - BOOST_ASSERT(entry); - OUTCOME_TRY(instance, entry->get().instantiate(lock)); - BOOST_ASSERT(shared_from_this()); - return std::make_shared( - weak_from_this(), state, std::move(instance)); - } - void RuntimeInstancesPoolImpl::release( - const RuntimeInstancesPoolImpl::TrieHash &state, + const CodeHash &code_hash, + const RuntimeContext::ContextParams &config, std::shared_ptr &&instance) { std::unique_lock guard{pools_mtx_}; - auto entry = pools_.get(state); + Key key{code_hash, config}; + auto entry = pools_.get(key); if (not entry) { - entry = pools_.put(state, {instance->getModule(), {}}); + entry = pools_.put(key, {instance->getModule(), {}}); } entry->get().instances.emplace_back(std::move(instance)); } - std::optional> - RuntimeInstancesPoolImpl::getModule( - const RuntimeInstancesPoolImpl::TrieHash &state) { - std::unique_lock guard{pools_mtx_}; - if (auto entry = pools_.get(state)) { - return entry->get().module; - } - return std::nullopt; - } - - void RuntimeInstancesPoolImpl::putModule( - const RuntimeInstancesPoolImpl::TrieHash &state, - std::shared_ptr module) { - std::unique_lock guard{pools_mtx_}; - if (not pools_.get(state)) { - pools_.put(state, {std::move(module), {}}); - } - } - outcome::result> RuntimeInstancesPoolImpl::InstancePool::instantiate( std::unique_lock &lock) { diff --git a/core/runtime/common/runtime_instances_pool.hpp b/core/runtime/common/runtime_instances_pool.hpp index 1ccdb7ccdf..44906ad9c0 100644 --- a/core/runtime/common/runtime_instances_pool.hpp +++ b/core/runtime/common/runtime_instances_pool.hpp @@ -14,7 +14,6 @@ #include #include -#include "runtime/common/stack_limiter.hpp" #include "runtime/module_factory.hpp" #include "utils/lru.hpp" @@ -38,18 +37,6 @@ namespace kagome::runtime { common::BufferView code_zstd, const RuntimeContext::ContextParams &config) override; - /** - * @brief Instantiate new or reuse existing ModuleInstance for the provided - * state. - * - * @param state - the merkle trie root of the state containing the code of - * the runtime module we are acquiring an instance of. - * @return pointer to the acquired ModuleInstance if success. Error - * otherwise. - */ - outcome::result> instantiateFromState( - const TrieHash &state, - const RuntimeContext::ContextParams &config) override; /** * @brief Releases the module instance (returns it to the pool) * @@ -57,29 +44,12 @@ namespace kagome::runtime { * module code we are releasing an instance of. * @param instance - instance to be released. */ - void release(const TrieHash &state, - std::shared_ptr &&instance) override; - - /** - * @brief Get the module for state from internal cache - * - * @param state - the state containing the module's code. - * @return Module if any, nullopt otherwise - */ - std::optional> getModule( - const TrieHash &state) override; - - /** - * @brief Puts new module into internal cache - * - * @param state - storage hash of the block containing the code of the - * module - * @param module - new module pointer - */ - void putModule(const TrieHash &state, - std::shared_ptr module) override; + void release(const CodeHash &code_hash, + const RuntimeContext::ContextParams &config, + std::shared_ptr &&instance); private: + using Key = std::tuple; struct InstancePool { std::shared_ptr module; std::vector> instances; @@ -99,10 +69,10 @@ namespace kagome::runtime { std::shared_ptr instrument_; std::mutex pools_mtx_; - Lru pools_; + Lru pools_; mutable std::mutex compiling_modules_mtx_; - std::unordered_map> + std::unordered_map> compiling_modules_; }; diff --git a/core/runtime/common/storage_code_provider.cpp b/core/runtime/common/storage_code_provider.cpp index 1d786517b1..28b1055e87 100644 --- a/core/runtime/common/storage_code_provider.cpp +++ b/core/runtime/common/storage_code_provider.cpp @@ -31,7 +31,7 @@ namespace kagome::runtime { BOOST_ASSERT(runtime_upgrade_tracker_ != nullptr); } - outcome::result StorageCodeProvider::getCodeAt( + RuntimeCodeProvider::Result StorageCodeProvider::getCodeAt( const storage::trie::RootHash &state) const { std::unique_lock lock{mutex_}; if (last_state_root_ != state) { @@ -42,13 +42,14 @@ namespace kagome::runtime { OUTCOME_TRY( code, chain_spec_->fetchCodeSubstituteByBlockInfo(block_info.value())); - OUTCOME_TRY(uncompressCodeIfNeeded(code, cached_code_)); - return cached_code_; + common::Buffer code2; + OUTCOME_TRY(uncompressCodeIfNeeded(code, code2)); + return std::make_shared(std::move(code2)); } } OUTCOME_TRY(batch, storage_->getEphemeralBatchAt(state)); OUTCOME_TRY(code, setCodeFromBatch(*batch.get())); - cached_code_ = std::move(code); + cached_code_ = std::make_shared(std::move(code)); last_state_root_ = state; } return cached_code_; diff --git a/core/runtime/common/storage_code_provider.hpp b/core/runtime/common/storage_code_provider.hpp index 5d8095dc12..c7416d244f 100644 --- a/core/runtime/common/storage_code_provider.hpp +++ b/core/runtime/common/storage_code_provider.hpp @@ -31,8 +31,7 @@ namespace kagome::runtime { std::shared_ptr code_substitutes, std::shared_ptr chain_spec); - outcome::result getCodeAt( - const storage::trie::RootHash &state) const override; + Result getCodeAt(const storage::trie::RootHash &state) const override; private: outcome::result setCodeFromBatch( @@ -41,7 +40,7 @@ namespace kagome::runtime { std::shared_ptr runtime_upgrade_tracker_; std::shared_ptr known_code_substitutes_; std::shared_ptr chain_spec_; - mutable common::Buffer cached_code_; + mutable Code cached_code_; mutable storage::trie::RootHash last_state_root_; log::Logger logger_; mutable std::mutex mutex_; diff --git a/core/runtime/heap_alloc_strategy.hpp b/core/runtime/heap_alloc_strategy.hpp new file mode 100644 index 0000000000..096c9ce77d --- /dev/null +++ b/core/runtime/heap_alloc_strategy.hpp @@ -0,0 +1,54 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +#include "scale/tie.hpp" +#include "scale/tie_hash.hpp" + +namespace kagome { + /** + * Allocate the initial heap pages as requested by the wasm file and then + * allow it to grow dynamically. + */ + struct HeapAllocStrategyDynamic { + SCALE_TIE(1); + SCALE_TIE_HASH_BOOST(HeapAllocStrategyDynamic); + + /** + * The absolute maximum size of the linear memory (in pages). + * When `Some(_)` the linear memory will be allowed to grow up to this + * limit. When `None` the linear memory will be allowed to grow up to the + * maximum limit supported by WASM (4GB). + */ + std::optional maximum_pages; + }; + /** + * Allocate a static number of heap pages. + * The total number of allocated heap pages is the initial number of heap + * pages requested by the wasm file plus the `extra_pages`. + */ + struct HeapAllocStrategyStatic { + SCALE_TIE(1); + SCALE_TIE_HASH_BOOST(HeapAllocStrategyStatic); + + /** + * The number of pages that will be added on top of the initial heap pages + * requested by the wasm file. + */ + uint32_t extra_pages; + }; + /** + * Defines the heap pages allocation strategy the wasm runtime should use. + * A heap page is defined as 64KiB of memory. + */ + using HeapAllocStrategy = + boost::variant; +} // namespace kagome diff --git a/core/runtime/heap_alloc_strategy_heappages.hpp b/core/runtime/heap_alloc_strategy_heappages.hpp new file mode 100644 index 0000000000..537901a1ca --- /dev/null +++ b/core/runtime/heap_alloc_strategy_heappages.hpp @@ -0,0 +1,25 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "runtime/heap_alloc_strategy.hpp" +#include "storage/predefined_keys.hpp" +#include "storage/trie/trie_batches.hpp" + +namespace kagome { + /// Convert ":heappages" from state trie to `HeapAllocStrategy`. + inline outcome::result> + heapAllocStrategyHeappages(const storage::trie::TrieBatch &trie) { + OUTCOME_TRY(raw, trie.tryGet(storage::kRuntimeHeappagesKey)); + if (raw) { + if (auto r = scale::decode(*raw)) { + return HeapAllocStrategyStatic{static_cast(r.value())}; + } + } + return std::nullopt; + } +} // namespace kagome diff --git a/core/runtime/memory.hpp b/core/runtime/memory.hpp index f226a5b344..0128719eca 100644 --- a/core/runtime/memory.hpp +++ b/core/runtime/memory.hpp @@ -52,6 +52,8 @@ namespace kagome::runtime { */ virtual WasmSize size() const = 0; + virtual std::optional pagesMax() const = 0; + /** * Resizes memory to the given size * @param new_size diff --git a/core/runtime/module.hpp b/core/runtime/module.hpp index 103bb6c461..60727a00f5 100644 --- a/core/runtime/module.hpp +++ b/core/runtime/module.hpp @@ -25,36 +25,7 @@ namespace kagome::runtime { public: virtual ~Module() = default; - virtual outcome::result> instantiate() const = 0; + virtual outcome::result> instantiate() + const = 0; }; - - /** - * A wrapper for compiled module. Used when we update WAVM runtime in order to - * skip double compilation which takes significant time (see issue #1104). - * Currently it's shared through DI. - */ - class SingleModuleCache { - public: - /** - * @brief Sets new cached value, scrapping previous if any - * @param module New compiled module to store - */ - void set(std::shared_ptr module) { - module_ = module; - } - - /** - * Pops stored module (if any), clears cache in process. - * @return Value if any, std::nullopt otherwise. - */ - std::optional> try_extract() { - auto module = module_; - module_.reset(); - return module; - } - - private: - std::optional> module_; - }; - } // namespace kagome::runtime diff --git a/core/runtime/runtime_api/impl/lru.hpp b/core/runtime/runtime_api/impl/lru.hpp index ee1a8c05bd..3bc0b26c91 100644 --- a/core/runtime/runtime_api/impl/lru.hpp +++ b/core/runtime/runtime_api/impl/lru.hpp @@ -11,6 +11,7 @@ #include "runtime/runtime_upgrade_tracker.hpp" #include "utils/lru_encoded.hpp" #include "utils/safe_object.hpp" +#include "utils/tuple_hash.hpp" namespace kagome::runtime { constexpr auto DISABLE_RUNTIME_LRU = false; @@ -163,6 +164,7 @@ template struct std::hash> { size_t operator()( const kagome::runtime::RuntimeApiLruBlockArgKey &x) const { - return boost::hash_value(std::tie(x.first, x.second)); + auto t = std::tie(x.first, x.second); + return std::hash{}(t); } }; diff --git a/core/runtime/runtime_code_provider.hpp b/core/runtime/runtime_code_provider.hpp index c3cde37a05..9ef92ec391 100644 --- a/core/runtime/runtime_code_provider.hpp +++ b/core/runtime/runtime_code_provider.hpp @@ -6,13 +6,11 @@ #pragma once -#include -#include - -#include "common/buffer_view.hpp" -#include "primitives/block_id.hpp" +#include "common/buffer.hpp" #include "storage/trie/types.hpp" +#include + namespace kagome::runtime { /** * @class RuntimeCodeProvider keeps and provides wasm state code @@ -21,8 +19,10 @@ namespace kagome::runtime { public: virtual ~RuntimeCodeProvider() = default; - virtual outcome::result getCodeAt( - const storage::trie::RootHash &state) const = 0; + using Code = std::shared_ptr; + using Result = outcome::result; + + virtual Result getCodeAt(const storage::trie::RootHash &state) const = 0; }; } // namespace kagome::runtime diff --git a/core/runtime/runtime_context.hpp b/core/runtime/runtime_context.hpp index beac171705..2093e4644a 100644 --- a/core/runtime/runtime_context.hpp +++ b/core/runtime/runtime_context.hpp @@ -159,3 +159,5 @@ namespace kagome::runtime { }; } // namespace kagome::runtime + +SCALE_TIE_HASH_STD(kagome::runtime::RuntimeContext::ContextParams); diff --git a/core/runtime/runtime_instances_pool.hpp b/core/runtime/runtime_instances_pool.hpp index 2ee68fc893..4d922e1ae8 100644 --- a/core/runtime/runtime_instances_pool.hpp +++ b/core/runtime/runtime_instances_pool.hpp @@ -19,7 +19,6 @@ namespace kagome::runtime { public: static constexpr size_t DEFAULT_MODULES_CACHE_SIZE = 2; - using TrieHash = storage::trie::RootHash; using CodeHash = storage::trie::RootHash; virtual ~RuntimeInstancesPool() = default; @@ -28,47 +27,6 @@ namespace kagome::runtime { instantiateFromCode(const CodeHash &code_hash, common::BufferView code_zstd, const RuntimeContext::ContextParams &config) = 0; - - /** - * @brief Instantiate new or reuse existing ModuleInstance for the provided - * state. - * - * @param state - the merkle trie root of the state containing the code of - * the runtime module we are acquiring an instance of. - * @return pointer to the acquired ModuleInstance if success. Error - * otherwise. - */ - virtual outcome::result> - instantiateFromState(const TrieHash &state, - const RuntimeContext::ContextParams &config) = 0; - /** - * @brief Releases the module instance (returns it to the pool) - * - * @param state - the merkle trie root of the state containing the runtime - * module code we are releasing an instance of. - * @param instance - instance to be released. - */ - virtual void release(const TrieHash &state, - std::shared_ptr &&instance) = 0; - - /** - * @brief Get the module for state from internal cache - * - * @param state - the state containing the module's code. - * @return Module if any, nullopt otherwise - */ - virtual std::optional> getModule( - const TrieHash &state) = 0; - - /** - * @brief Puts new module into internal cache - * - * @param state - storage hash of the block containing the code of the - * module - * @param module - new module pointer - */ - virtual void putModule(const TrieHash &state, - std::shared_ptr module) = 0; }; } // namespace kagome::runtime diff --git a/core/runtime/types.hpp b/core/runtime/types.hpp index 68d659da88..659f30ff13 100644 --- a/core/runtime/types.hpp +++ b/core/runtime/types.hpp @@ -11,6 +11,7 @@ #include #include "outcome/outcome.hpp" +#include "runtime/heap_alloc_strategy.hpp" #include "scale/tie.hpp" namespace kagome::runtime { @@ -53,7 +54,7 @@ namespace kagome::runtime { SCALE_TIE(2); std::optional max_stack_values_num{}; - std::optional max_memory_pages_num{}; + HeapAllocStrategy heap_alloc_strategy; }; struct MemoryConfig { @@ -77,9 +78,11 @@ namespace kagome::runtime { enum class Error { COMPILATION_FAILED = 1, - INSTRUMENTATION_FAILED + INSTRUMENTATION_FAILED, }; } // namespace kagome::runtime OUTCOME_HPP_DECLARE_ERROR(kagome::runtime, Error); + +SCALE_TIE_HASH_STD(kagome::runtime::MemoryLimits); diff --git a/core/runtime/wabt/CMakeLists.txt b/core/runtime/wabt/CMakeLists.txt new file mode 100644 index 0000000000..2e5a248393 --- /dev/null +++ b/core/runtime/wabt/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +add_library(wasm_instrument + instrument.cpp + stack_limiter.cpp + ) +target_link_libraries(wasm_instrument + logger + outcome + wabt::wabt + ) +kagome_install(wasm_instrument) diff --git a/core/runtime/wabt/error.hpp b/core/runtime/wabt/error.hpp new file mode 100644 index 0000000000..c1cac23d3e --- /dev/null +++ b/core/runtime/wabt/error.hpp @@ -0,0 +1,29 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "runtime/types.hpp" + +namespace kagome::runtime { + struct WabtError { + [[nodiscard]] const std::string &message() const { + return msg; + } + std::string msg; + }; + + inline std::error_code make_error_code(const WabtError &) { + return Error::INSTRUMENTATION_FAILED; + } + + inline void outcome_throw_as_system_error_with_payload(WabtError e) { + throw e; + } + + template + using WabtOutcome = outcome::result; +} // namespace kagome::runtime diff --git a/core/runtime/wabt/instrument.cpp b/core/runtime/wabt/instrument.cpp new file mode 100644 index 0000000000..f226185372 --- /dev/null +++ b/core/runtime/wabt/instrument.cpp @@ -0,0 +1,101 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "runtime/wabt/instrument.hpp" + +#include "runtime/wabt/stack_limiter.hpp" +#include "runtime/wabt/util.hpp" + +namespace kagome::runtime { + WabtOutcome convertMemoryImportIntoExport(wabt::Module &module) { + for (auto it = module.fields.begin(); it != module.fields.end(); ++it) { + auto import = dynamic_cast(&*it); + if (not import) { + continue; + } + auto memory = dynamic_cast(import->import.get()); + if (not memory) { + continue; + } + if (std::any_of(module.fields.begin(), + module.fields.end(), + [&](const wabt::ModuleField &field) { + return field.type() == wabt::ModuleFieldType::Memory; + })) { + return WabtError{"unexpected MemoryModuleField"}; + } + auto import_it = std::find( + module.imports.begin(), module.imports.end(), import->import.get()); + if (import_it == module.imports.end()) { + return WabtError{"inconsistent Module.imports"}; + } + auto memory_it = std::find( + module.memories.begin(), module.memories.end(), &memory->memory); + if (memory_it == module.memories.end()) { + return WabtError{"inconsistent Module.memories"}; + } + auto memory2 = std::make_unique(); + memory2->memory.page_limits = memory->memory.page_limits; + auto export_ = std::make_unique(); + export_->export_ = { + import->import->field_name, + wabt::ExternalKind::Memory, + wabt::Var{0, {}}, + }; + module.imports.erase(import_it); + module.memories.erase(memory_it); + module.fields.erase(it); + --module.num_memory_imports; + module.AppendField(std::move(memory2)); + module.AppendField(std::move(export_)); + break; + } + return outcome::success(); + } + + WabtOutcome setupMemoryAccordingToHeapAllocStrategy( + wabt::Module &module, const HeapAllocStrategy &config) { + for (auto it = module.fields.begin(); it != module.fields.end(); ++it) { + auto memory = dynamic_cast(&*it); + if (not memory) { + continue; + } + auto &limit = memory->memory.page_limits; + auto init = limit.initial; + if (auto v = boost::get(&config)) { + if (auto &max = v->maximum_pages) { + limit = {std::min(init, *max), *max}; + } else { + limit = wabt::Limits{init}; + } + } else { + auto max = + init + boost::get(config).extra_pages; + limit = {max, max}; + } + } + return outcome::success(); + } + + WabtOutcome prepareBlobForCompilation( + common::BufferView code, const MemoryLimits &config) { + OUTCOME_TRY(module, wabtDecode(code)); + if (config.max_stack_values_num) { + OUTCOME_TRY( + instrumentWithStackLimiter(module, *config.max_stack_values_num)); + } + OUTCOME_TRY(convertMemoryImportIntoExport(module)); + OUTCOME_TRY(setupMemoryAccordingToHeapAllocStrategy( + module, config.heap_alloc_strategy)); + OUTCOME_TRY(wabtValidate(module)); + return wabtEncode(module); + } + + WabtOutcome InstrumentWasm::instrument( + common::BufferView code, const MemoryLimits &config) const { + return prepareBlobForCompilation(code, config); + } +} // namespace kagome::runtime diff --git a/core/runtime/wabt/instrument.hpp b/core/runtime/wabt/instrument.hpp new file mode 100644 index 0000000000..eaf56c971d --- /dev/null +++ b/core/runtime/wabt/instrument.hpp @@ -0,0 +1,41 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "common/buffer.hpp" +#include "runtime/wabt/error.hpp" + +namespace wabt { + struct Module; +} // namespace wabt + +namespace kagome::runtime { + struct MemoryLimits; + + WabtOutcome convertMemoryImportIntoExport(wabt::Module &module); + + WabtOutcome setupMemoryAccordingToHeapAllocStrategy( + wabt::Module &module, const HeapAllocStrategy &config); + + /** + * Instrument wasm code: + * - add stack limiting + * - convert imported memory (if any) to exported memory + * - set memory limit + * https://github.com/paritytech/polkadot-sdk/blob/11831df8e709061e9c6b3292facb5d7d9709f151/substrate/client/executor/wasmtime/src/runtime.rs#L651 + */ + WabtOutcome prepareBlobForCompilation( + common::BufferView code, const MemoryLimits &config); + + class InstrumentWasm { + public: + virtual ~InstrumentWasm() = default; + + virtual WabtOutcome instrument( + common::BufferView code, const MemoryLimits &config) const; + }; +} // namespace kagome::runtime diff --git a/core/runtime/common/stack_limiter.cpp b/core/runtime/wabt/stack_limiter.cpp similarity index 88% rename from core/runtime/common/stack_limiter.cpp rename to core/runtime/wabt/stack_limiter.cpp index 0684dcc08b..d37d38d6b9 100644 --- a/core/runtime/common/stack_limiter.cpp +++ b/core/runtime/wabt/stack_limiter.cpp @@ -4,20 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "runtime/common/stack_limiter.hpp" +#include "runtime/wabt/stack_limiter.hpp" #include "common/visitor.hpp" #include "log/logger.hpp" #include "log/profiling_logger.hpp" - -#include -#include -#include -#include -#include +#include "runtime/wabt/util.hpp" namespace kagome::runtime { - namespace detail { template @@ -119,9 +113,9 @@ namespace kagome::runtime { frames_{}, logger_{std::move(logger)} {} - outcome::result unreachable() { + WabtOutcome unreachable() { if (frames_.empty()) { - return StackLimiterError{"Stack must not be empty"}; + return WabtError{"Stack must not be empty"}; } frames_.back().is_polymorphic = true; return outcome::success(); @@ -148,8 +142,8 @@ namespace kagome::runtime { }); } - outcome::result push_frame( - MaybeConst branch, bool check_frame_boundary) { + WabtOutcome push_frame(MaybeConst branch, + bool check_frame_boundary) { uint32_t end_arity = branch.true_.decl.GetNumResults() != 0; uint32_t branch_arity = end_arity; auto res = pop_values(1); @@ -178,9 +172,9 @@ namespace kagome::runtime { }); } - outcome::result pop_frame() { + WabtOutcome pop_frame() { if (frames_.empty()) { - return StackLimiterError{"Stack is empty"}; + return WabtError{"Stack is empty"}; } height_ = frames_.back().start_height; push_values(frames_.back().end_value_num); @@ -202,24 +196,23 @@ namespace kagome::runtime { SL_TRACE(logger_, "push {}, now height_ {}", num, height_); } - outcome::result pop_values(uint32_t num) { + WabtOutcome pop_values(uint32_t num) { if (num == 0) { return outcome::success(); } if (frames_.empty()) { - return StackLimiterError{"Stack is empty"}; + return WabtError{"Stack is empty"}; } SL_TRACE(logger_, "pop {}, now height_ {}", num, height_ - num); if (height_ - num < frames_.back().start_height) { if (!frames_.back().is_polymorphic) { - return StackLimiterError{ - "Popping values not pushed in the current frame"}; + return WabtError{"Popping values not pushed in the current frame"}; } else { return outcome::success(); } } if (height_ < num) { - return StackLimiterError{"Stack underflow"}; + return WabtError{"Stack underflow"}; } height_ -= num; return outcome::success(); @@ -229,7 +222,7 @@ namespace kagome::runtime { return frames_.empty(); } - outcome::result advance() { + WabtOutcome advance() { bool is_over = false; do { auto &frame = frames_.back(); @@ -273,11 +266,10 @@ namespace kagome::runtime { return &frames_.back(); } - [[nodiscard]] outcome::result, - StackLimiterError> + [[nodiscard]] WabtOutcome> get_frame(size_t idx_from_top) const { if (frames_.size() <= idx_from_top) { - return StackLimiterError{"Stack frame underflow"}; + return WabtError{"Stack frame underflow"}; } return frames_.at(frames_.size() - idx_from_top - 1); } @@ -291,10 +283,9 @@ namespace kagome::runtime { using ConstStack = Stack; using MutStack = Stack; - outcome::result compute_stack_cost( - const log::Logger &logger, - const wabt::Func &func, - const wabt::Module &module) { + WabtOutcome compute_stack_cost(const log::Logger &logger, + const wabt::Func &func, + const wabt::Module &module) { uint32_t locals_num = func.GetNumLocals(); ConstStack stack{logger}; @@ -374,7 +365,7 @@ namespace kagome::runtime { OUTCOME_TRY(frame, stack.get_frame(v.index())); uint32_t arity = frame.get().branch_value_num; if (arity != target_arity) { - return StackLimiterError{ + return WabtError{ "All jump-targets should have equal frame arities"}; } } @@ -491,9 +482,8 @@ namespace kagome::runtime { break; } default: - return StackLimiterError{ - fmt::format("Unsupported instruction: {}", - wabt::GetExprTypeName(expr.type()))}; + return WabtError{fmt::format("Unsupported instruction: {}", + wabt::GetExprTypeName(expr.type()))}; } if (!pushed_frame) { if (auto expr_opt = stack.advance(); !expr_opt.has_value()) { @@ -555,7 +545,7 @@ namespace kagome::runtime { return next_it; } - outcome::result instrument_func( + WabtOutcome instrument_func( wabt::Func &func, const wabt::Var &stack_height, uint32_t stack_limit, @@ -628,7 +618,7 @@ namespace kagome::runtime { return outcome::success(); } - outcome::result generate_thunks( + WabtOutcome generate_thunks( const log::Logger &logger, wabt::Module &module, const wabt::Var &stack_height, @@ -666,7 +656,7 @@ namespace kagome::runtime { break; } default: - return StackLimiterError{ + return WabtError{ fmt::format("Unsupported element expression of type {}", GetExprTypeName(expr.type()))}; } @@ -730,7 +720,7 @@ namespace kagome::runtime { break; } default: - return StackLimiterError{ + return WabtError{ fmt::format("Invalid element expression of type {}", GetExprTypeName(expr.type()))}; } @@ -743,27 +733,15 @@ namespace kagome::runtime { return outcome::success(); } } // namespace detail - outcome::result instrumentWithStackLimiter( - common::BufferView uncompressed_wasm, const size_t stack_limit) { - auto logger = log::createLogger("StackLimiter", "runtime"); - KAGOME_PROFILE_START_L(logger, read_ir); - wabt::Errors errors; - wabt::Module module; - wabt::ReadBinaryIr("", - uncompressed_wasm.data(), - uncompressed_wasm.size(), - wabt::ReadBinaryOptions({}, nullptr, true, false, false), - &errors, - &module); - if (!errors.empty()) { - std::stringstream ss; - for (auto &e : errors) { - ss << e.message << "\n"; - } - return StackLimiterError{ss.str()}; - } - KAGOME_PROFILE_END_L(logger, read_ir); + auto &stackLimiterLog() { + static auto log = log::createLogger("StackLimiter", "runtime"); + return log; + } + + WabtOutcome instrumentWithStackLimiter(wabt::Module &module, + size_t stack_limit) { + auto logger = stackLimiterLog(); KAGOME_PROFILE_START_L(logger, count_costs); std::unordered_map func_costs; for (size_t i = 0; i < module.num_func_imports; i++) { @@ -802,32 +780,20 @@ namespace kagome::runtime { KAGOME_PROFILE_END_L(logger, instrument_wasm); - if (wabt::Failed( - wabt::ValidateModule(&module, &errors, wabt::ValidateOptions{}))) { - std::stringstream ss; - for (auto &err : errors) { - ss << err.message; - } - return StackLimiterError{ss.str()}; - } - - KAGOME_PROFILE_START_L(logger, serialize_wasm); - wabt::MemoryStream s; - if (wabt::WriteBinaryModule( - &s, &module, wabt::WriteBinaryOptions({}, false, false, true)) - != wabt::Result::Ok) { - return StackLimiterError{"Failed to serialize WASM module"}; - } - KAGOME_PROFILE_END_L(logger, serialize_wasm); - return common::Buffer{std::move(s.output_buffer().data)}; + OUTCOME_TRY(wabtValidate(module)); + return outcome::success(); } - WabtOutcome InstrumentWasm::instrument( - common::BufferView code, const MemoryLimits &config) const { - // TODO(turuslan): https://github.com/qdrvm/kagome/pull/2009 - if (config.max_stack_values_num) { - return instrumentWithStackLimiter(code, *config.max_stack_values_num); - } - return common::Buffer{code}; + WabtOutcome instrumentWithStackLimiter( + common::BufferView uncompressed_wasm, size_t stack_limit) { + auto logger = stackLimiterLog(); + KAGOME_PROFILE_START_L(logger, read_ir); + OUTCOME_TRY(module, wabtDecode(uncompressed_wasm)); + KAGOME_PROFILE_END_L(logger, read_ir); + + OUTCOME_TRY(instrumentWithStackLimiter(module, stack_limit)); + + KAGOME_PROFILE_START_L(logger, serialize_wasm); + return wabtEncode(module); } } // namespace kagome::runtime diff --git a/core/runtime/common/stack_limiter.hpp b/core/runtime/wabt/stack_limiter.hpp similarity index 51% rename from core/runtime/common/stack_limiter.hpp rename to core/runtime/wabt/stack_limiter.hpp index 40403b7ad4..d745b31f9f 100644 --- a/core/runtime/common/stack_limiter.hpp +++ b/core/runtime/wabt/stack_limiter.hpp @@ -6,12 +6,9 @@ #pragma once -#include - #include "common/buffer.hpp" #include "log/logger.hpp" -#include "outcome/outcome.hpp" -#include "runtime/types.hpp" +#include "runtime/wabt/error.hpp" namespace wabt { struct Module; @@ -19,27 +16,11 @@ namespace wabt { } // namespace wabt namespace kagome::runtime { - - struct StackLimiterError { - [[nodiscard]] const std::string &message() const { - return msg; - } - std::string msg; - }; - - template - using WabtOutcome = outcome::result; - // for tests namespace detail { - outcome::result compute_stack_cost( - const log::Logger &logger, - const wabt::Func &func, - const wabt::Module &module); - } - - inline boost::exception_ptr make_exception_ptr(const StackLimiterError &e) { - return std::make_exception_ptr(std::runtime_error{e.msg}); + WabtOutcome compute_stack_cost(const log::Logger &logger, + const wabt::Func &func, + const wabt::Module &module); } /** @@ -54,11 +35,6 @@ namespace kagome::runtime { [[nodiscard]] WabtOutcome instrumentWithStackLimiter( common::BufferView uncompressed_wasm, size_t stack_limit); - class InstrumentWasm { - public: - virtual ~InstrumentWasm() = default; - - virtual WabtOutcome instrument( - common::BufferView code, const MemoryLimits &config) const; - }; + WabtOutcome instrumentWithStackLimiter(wabt::Module &module, + size_t stack_limit); } // namespace kagome::runtime diff --git a/core/runtime/wabt/util.hpp b/core/runtime/wabt/util.hpp new file mode 100644 index 0000000000..3e5c7e63c8 --- /dev/null +++ b/core/runtime/wabt/util.hpp @@ -0,0 +1,53 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "common/buffer.hpp" +#include "runtime/wabt/error.hpp" + +namespace kagome::runtime { + WabtOutcome wabtTry(auto &&f) { + wabt::Errors errors; + if (wabt::Failed(f(errors))) { + return WabtError{ + wabt::FormatErrorsToString(errors, wabt::Location::Type::Binary)}; + } + return outcome::success(); + } + + inline WabtOutcome wabtDecode(common::BufferView code) { + wabt::Module module; + OUTCOME_TRY(wabtTry([&](wabt::Errors &errors) { + return wabt::ReadBinaryIr( + "", + code.data(), + code.size(), + wabt::ReadBinaryOptions({}, nullptr, true, false, false), + &errors, + &module); + })); + return module; + } + + inline WabtOutcome wabtValidate(const wabt::Module &module) { + return wabtTry([&](wabt::Errors &errors) { + return wabt::ValidateModule(&module, &errors, wabt::ValidateOptions{}); + }); + } + + inline WabtOutcome wabtEncode(const wabt::Module &module) { + wabt::MemoryStream s; + if (wabt::Failed(wabt::WriteBinaryModule( + &s, &module, wabt::WriteBinaryOptions({}, false, false, true)))) { + return WabtError{"Failed to serialize WASM module"}; + } + return common::Buffer{std::move(s.output_buffer().data)}; + } +} // namespace kagome::runtime diff --git a/core/runtime/wasm_edge/memory_impl.cpp b/core/runtime/wasm_edge/memory_impl.cpp index 8c330f7ec1..1fcd2317cc 100644 --- a/core/runtime/wasm_edge/memory_impl.cpp +++ b/core/runtime/wasm_edge/memory_impl.cpp @@ -20,6 +20,16 @@ namespace kagome::runtime::wasm_edge { fmt::ptr(mem_instance_)); } + std::optional MemoryImpl::pagesMax() const { + auto type = WasmEdge_MemoryInstanceGetMemoryType(mem_instance_); + if (type == nullptr) { + throw std::runtime_error{ + "WasmEdge_MemoryInstanceGetMemoryType returned nullptr"}; + } + auto limit = WasmEdge_MemoryTypeGetLimit(type); + return limit.HasMax ? std::make_optional(limit.Max) : std::nullopt; + } + void MemoryImpl::resize(WasmSize new_size) { if (new_size > size()) { auto old_page_num = WasmEdge_MemoryInstanceGetPageSize(mem_instance_); diff --git a/core/runtime/wasm_edge/memory_impl.hpp b/core/runtime/wasm_edge/memory_impl.hpp index d1be855ebd..943ab109d8 100644 --- a/core/runtime/wasm_edge/memory_impl.hpp +++ b/core/runtime/wasm_edge/memory_impl.hpp @@ -29,6 +29,8 @@ namespace kagome::runtime::wasm_edge { * kMemoryPageSize; } + std::optional pagesMax() const override; + /** * Resizes memory to the given size * @param new_size diff --git a/core/runtime/wavm/CMakeLists.txt b/core/runtime/wavm/CMakeLists.txt index 59874229fd..726d2f1fc3 100644 --- a/core/runtime/wavm/CMakeLists.txt +++ b/core/runtime/wavm/CMakeLists.txt @@ -24,7 +24,6 @@ target_link_libraries(runtime_wavm runtime_common ${LLVM_LIBS} WAVM::libWAVM - constant_code_provider core_api_factory executor Boost::boost diff --git a/core/runtime/wavm/instance_environment_factory.hpp b/core/runtime/wavm/instance_environment_factory.hpp index 0b2b19511c..bec26db93d 100644 --- a/core/runtime/wavm/instance_environment_factory.hpp +++ b/core/runtime/wavm/instance_environment_factory.hpp @@ -26,7 +26,6 @@ namespace WAVM::Runtime { } namespace kagome::runtime { - class SingleModuleCache; class ModuleFactory; } // namespace kagome::runtime @@ -53,7 +52,6 @@ namespace kagome::runtime::wavm { std::shared_ptr serializer_; std::shared_ptr host_api_factory_; std::shared_ptr module_factory_; - }; } // namespace kagome::runtime::wavm diff --git a/core/runtime/wavm/memory_impl.cpp b/core/runtime/wavm/memory_impl.cpp index 2decdfe5a8..e5a23e21e2 100644 --- a/core/runtime/wavm/memory_impl.cpp +++ b/core/runtime/wavm/memory_impl.cpp @@ -23,6 +23,11 @@ namespace kagome::runtime::wavm { resize(kInitialMemorySize); } + std::optional MemoryImpl::pagesMax() const { + auto max = WAVM::Runtime::getMemoryType(memory_).size.max; + return max != UINT64_MAX ? std::make_optional(max) : std::nullopt; + } + WasmPointer MemoryImpl::allocate(WasmSize size) { return allocator_->allocate(size); } diff --git a/core/runtime/wavm/memory_impl.hpp b/core/runtime/wavm/memory_impl.hpp index 27883f2e5f..24f5d21e68 100644 --- a/core/runtime/wavm/memory_impl.hpp +++ b/core/runtime/wavm/memory_impl.hpp @@ -42,6 +42,8 @@ namespace kagome::runtime::wavm { return WAVM::Runtime::getMemoryNumPages(memory_) * kMemoryPageSize; } + std::optional pagesMax() const override; + void resize(WasmSize new_size) override { /** * We use this condition to avoid deallocated_ pointers fixup diff --git a/core/runtime/wavm/module_factory_impl.cpp b/core/runtime/wavm/module_factory_impl.cpp index 0e8ff45fd2..bf7dddf89a 100644 --- a/core/runtime/wavm/module_factory_impl.cpp +++ b/core/runtime/wavm/module_factory_impl.cpp @@ -21,7 +21,6 @@ namespace kagome::runtime::wavm { std::shared_ptr storage, std::shared_ptr serializer, std::shared_ptr intrinsic_module, - std::shared_ptr last_compiled_module, std::optional> module_cache, std::shared_ptr hasher) : compartment_{std::move(compartment)}, @@ -29,13 +28,11 @@ namespace kagome::runtime::wavm { host_api_factory_{host_api_factory}, storage_{storage}, serializer_{serializer}, - last_compiled_module_{last_compiled_module}, intrinsic_module_{std::move(intrinsic_module)}, hasher_(std::move(hasher)) { BOOST_ASSERT(compartment_ != nullptr); BOOST_ASSERT(module_params_ != nullptr); BOOST_ASSERT(host_api_factory_ != nullptr); - BOOST_ASSERT(last_compiled_module_ != nullptr); BOOST_ASSERT(intrinsic_module_ != nullptr); BOOST_ASSERT(hasher_ != nullptr); diff --git a/core/runtime/wavm/module_factory_impl.hpp b/core/runtime/wavm/module_factory_impl.hpp index 2e31cd86d1..242951d3f8 100644 --- a/core/runtime/wavm/module_factory_impl.hpp +++ b/core/runtime/wavm/module_factory_impl.hpp @@ -27,10 +27,6 @@ namespace kagome::storage::trie { class TrieSerializer; } // namespace kagome::storage::trie -namespace kagome::runtime { - class SingleModuleCache; -} - namespace kagome::runtime::wavm { class CompartmentWrapper; @@ -50,7 +46,6 @@ namespace kagome::runtime::wavm { std::shared_ptr storage, std::shared_ptr serializer, std::shared_ptr intrinsic_module, - std::shared_ptr last_compiled_module, std::optional> module_cache, std::shared_ptr hasher); @@ -63,7 +58,6 @@ namespace kagome::runtime::wavm { std::shared_ptr host_api_factory_; std::shared_ptr storage_; std::shared_ptr serializer_; - std::shared_ptr last_compiled_module_; std::shared_ptr intrinsic_module_; std::shared_ptr hasher_; }; diff --git a/core/scale/tie_hash.hpp b/core/scale/tie_hash.hpp new file mode 100644 index 0000000000..894afd27ab --- /dev/null +++ b/core/scale/tie_hash.hpp @@ -0,0 +1,28 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "scale/tie.hpp" +#include "utils/tuple_hash.hpp" + +namespace scale { + auto tieHash(const auto &v) { + return ::scale::as_tie(v, + [](auto t) { return std::hash{}(t); }); + } +} // namespace scale + +#define SCALE_TIE_HASH_BOOST(type) \ + friend auto hash_value(const type &v) { return ::scale::tieHash(v); } + +#define SCALE_TIE_HASH_STD(type) \ + template <> \ + struct std::hash { \ + inline size_t operator()(const type &v) const { \ + return ::scale::tieHash(v); \ + } \ + } diff --git a/core/storage/changes_trie/impl/storage_changes_tracker_impl.cpp b/core/storage/changes_trie/impl/storage_changes_tracker_impl.cpp index 2dbca67624..9d18be020d 100644 --- a/core/storage/changes_trie/impl/storage_changes_tracker_impl.cpp +++ b/core/storage/changes_trie/impl/storage_changes_tracker_impl.cpp @@ -14,7 +14,8 @@ namespace kagome::storage::changes_trie { const primitives::events::StorageSubscriptionEnginePtr &storage_sub_engine, const primitives::events::ChainSubscriptionEnginePtr &chain_sub_engine) { - if (actual_val_.find(storage::kRuntimeCodeKey) != actual_val_.cend()) { + if (actual_val_.contains(storage::kRuntimeCodeKey) + or actual_val_.contains(storage::kRuntimeHeappagesKey)) { chain_sub_engine->notify(primitives::events::ChainEventType::kNewRuntime, hash); } diff --git a/core/storage/predefined_keys.hpp b/core/storage/predefined_keys.hpp index f4680fa184..d837ef2f41 100644 --- a/core/storage/predefined_keys.hpp +++ b/core/storage/predefined_keys.hpp @@ -14,6 +14,7 @@ namespace kagome::storage { using namespace common::literals; inline const common::Buffer kRuntimeCodeKey = ":code"_buf; + inline const common::Buffer kRuntimeHeappagesKey = ":heappages"_buf; inline const common::Buffer kExtrinsicIndexKey = ":extrinsic_index"_buf; diff --git a/core/utils/tuple_hash.hpp b/core/utils/tuple_hash.hpp index 1b43dbd22a..3d75f63608 100644 --- a/core/utils/tuple_hash.hpp +++ b/core/utils/tuple_hash.hpp @@ -16,8 +16,9 @@ struct std::hash> { template void hash_element_of_tuple(size_t &result, const std::tuple &v) const { - auto item = std::get(v); - boost::hash_combine(result, std::hash()(item)); + auto &item = std::get(v); + boost::hash_combine(result, + std::hash>()(item)); if constexpr (sizeof...(Args) > I + 1) { hash_element_of_tuple(result, v); } diff --git a/test/core/runtime/CMakeLists.txt b/test/core/runtime/CMakeLists.txt index 1b31de72a7..442339ce3d 100644 --- a/test/core/runtime/CMakeLists.txt +++ b/test/core/runtime/CMakeLists.txt @@ -81,7 +81,7 @@ target_link_libraries(instance_pool_test addtest(stack_limiter_test stack_limiter_test.cpp) target_link_libraries(stack_limiter_test - runtime_common logger log_configurator + wasm_instrument ) diff --git a/test/core/runtime/binaryen/CMakeLists.txt b/test/core/runtime/binaryen/CMakeLists.txt index f3614c1a06..cc988c1445 100644 --- a/test/core/runtime/binaryen/CMakeLists.txt +++ b/test/core/runtime/binaryen/CMakeLists.txt @@ -85,7 +85,6 @@ addtest(runtime_external_interface_test target_link_libraries(runtime_external_interface_test binaryen_runtime_external_interface executor - constant_code_provider blob logger_for_tests ) diff --git a/test/core/runtime/binaryen/block_builder_api_test.cpp b/test/core/runtime/binaryen/block_builder_api_test.cpp index 2ce37dc100..c3ab1d339c 100644 --- a/test/core/runtime/binaryen/block_builder_api_test.cpp +++ b/test/core/runtime/binaryen/block_builder_api_test.cpp @@ -64,6 +64,7 @@ TEST_F(BlockBuilderApiTest, CheckInherents) { */ TEST_F(BlockBuilderApiTest, ApplyExtrinsic) { preparePersistentStorageExpects(); + prepareEphemeralStorageExpects(); createBlock("block_hash_43"_hash256, 43); auto ctx = ctx_factory_->persistentAt("block_hash_43"_hash256, std::nullopt).value(); @@ -88,6 +89,7 @@ TEST_F(BlockBuilderApiTest, DISABLED_RandomSeed){ */ TEST_F(BlockBuilderApiTest, InherentExtrinsics) { preparePersistentStorageExpects(); + prepareEphemeralStorageExpects(); createBlock("block_hash_44"_hash256, 44); auto ctx = ctx_factory_->persistentAt("block_hash_44"_hash256, std::nullopt).value(); diff --git a/test/core/runtime/binaryen/runtime_external_interface_test.cpp b/test/core/runtime/binaryen/runtime_external_interface_test.cpp index d79657c002..5c4ceb40fc 100644 --- a/test/core/runtime/binaryen/runtime_external_interface_test.cpp +++ b/test/core/runtime/binaryen/runtime_external_interface_test.cpp @@ -19,7 +19,6 @@ #include "mock/core/runtime/module_repository_mock.hpp" #include "mock/core/runtime/runtime_context_factory_mock.hpp" #include "mock/core/runtime/trie_storage_provider_mock.hpp" -#include "runtime/common/constant_code_provider.hpp" #include "runtime/common/memory_allocator.hpp" #include "runtime/ptr_size.hpp" #include "testutil/prepare_loggers.hpp" @@ -34,7 +33,6 @@ using kagome::crypto::KeyTypes; using kagome::host_api::HostApi; using kagome::host_api::HostApiFactoryMock; using kagome::host_api::HostApiMock; -using kagome::runtime::ConstantCodeProvider; using kagome::runtime::CoreApiFactoryMock; using kagome::runtime::MemoryProviderMock; using kagome::runtime::ModuleRepositoryMock; @@ -89,8 +87,6 @@ class REITest : public ::testing::Test { storage_provider_ = std::make_shared(); core_api_factory_ = std::make_shared(); memory_provider_ = std::make_shared(); - auto code_provider = - std::make_shared(kagome::common::Buffer{}); auto module_repo = std::make_shared(); auto header_repo = std::make_shared(); } diff --git a/test/core/runtime/binaryen/wasm_memory_test.cpp b/test/core/runtime/binaryen/wasm_memory_test.cpp index 4f3881ab7f..c05ed1df0b 100644 --- a/test/core/runtime/binaryen/wasm_memory_test.cpp +++ b/test/core/runtime/binaryen/wasm_memory_test.cpp @@ -35,10 +35,7 @@ class BinaryenMemoryHeapTest : public ::testing::Test { std::make_unique(host_api); memory_ = std::make_unique( - rei_->getMemory(), - runtime::MemoryConfig{ - kDefaultHeapBase, - runtime::MemoryLimits{.max_memory_pages_num = memory_page_limit_}}); + rei_->getMemory(), runtime::MemoryConfig{kDefaultHeapBase, {}}); } void TearDown() override { diff --git a/test/core/runtime/executor_test.cpp b/test/core/runtime/executor_test.cpp index 255c8632fd..c0a5b5bc6b 100644 --- a/test/core/runtime/executor_test.cpp +++ b/test/core/runtime/executor_test.cpp @@ -119,11 +119,6 @@ class ExecutorTest : public testing::Test { EXPECT_CALL(*storage_provider, setToEphemeralAt(storage_state)) .WillOnce(Return(outcome::success())); } - auto batch = std::make_shared(); - EXPECT_CALL(*storage_provider, getCurrentBatch()).WillOnce(Return(batch)); - static const auto heappages = ":heappages"_buf; - EXPECT_CALL(*batch, tryGetMock(heappages.view())) - .WillOnce(Return(kagome::common::Buffer{})); auto env = std::make_shared( memory_provider, storage_provider, nullptr, nullptr); diff --git a/test/core/runtime/instance_pool_test.cpp b/test/core/runtime/instance_pool_test.cpp index d3ca8d6f5e..a584573a9b 100644 --- a/test/core/runtime/instance_pool_test.cpp +++ b/test/core/runtime/instance_pool_test.cpp @@ -28,6 +28,7 @@ using kagome::runtime::ModuleMock; using kagome::runtime::RuntimeContext; using kagome::runtime::RuntimeInstancesPool; using kagome::runtime::RuntimeInstancesPoolImpl; +using testing::Return; RuntimeInstancesPool::CodeHash make_code_hash(int i) { return RuntimeInstancesPool::CodeHash::fromString( diff --git a/test/core/runtime/runtime_test_base.hpp b/test/core/runtime/runtime_test_base.hpp index a01d0b31f1..db4437aa3d 100644 --- a/test/core/runtime/runtime_test_base.hpp +++ b/test/core/runtime/runtime_test_base.hpp @@ -26,7 +26,6 @@ #include "mock/core/application/app_state_manager_mock.hpp" #include "mock/core/blockchain/block_header_repository_mock.hpp" #include "mock/core/blockchain/block_storage_mock.hpp" -#include "mock/core/crypto/hasher_mock.hpp" #include "mock/core/offchain/offchain_persistent_storage_mock.hpp" #include "mock/core/offchain/offchain_worker_pool_mock.hpp" #include "mock/core/runtime/runtime_properties_cache_mock.hpp" @@ -47,6 +46,7 @@ #include "runtime/executor.hpp" #include "runtime/module.hpp" #include "runtime/runtime_context.hpp" +#include "runtime/wabt/instrument.hpp" #include "storage/in_memory/in_memory_storage.hpp" #include "testutil/literals.hpp" #include "testutil/outcome.hpp" @@ -139,7 +139,6 @@ class RuntimeTestBase : public ::testing::Test { initStorage(); trie_storage_ = std::make_shared(); serializer_ = std::make_shared(); - hasher_ = std::make_shared(); auto buffer_storage = std::make_shared(); auto spaced_storage = std::make_shared(); @@ -171,9 +170,10 @@ class RuntimeTestBase : public ::testing::Test { auto module_repo = std::make_shared( std::make_shared( module_factory, std::make_shared()), + hasher_, upgrade_tracker, + trie_storage_, module_factory, - std::make_shared(), wasm_provider_); ctx_factory_ = std::make_shared( @@ -217,7 +217,8 @@ class RuntimeTestBase : public ::testing::Test { return cursor; })); static auto heappages_key = ":heappages"_buf; - EXPECT_CALL(batch, tryGetMock(heappages_key.view())); + EXPECT_CALL(batch, tryGetMock(heappages_key.view())) + .Times(testing::AnyNumber()); } primitives::BlockHeader createBlockHeader(const primitives::BlockHash &hash, @@ -262,6 +263,6 @@ class RuntimeTestBase : public ::testing::Test { std::shared_ptr ctx_factory_; std::shared_ptr offchain_storage_; std::shared_ptr offchain_worker_pool_; - std::shared_ptr hasher_; + std::shared_ptr hasher_; std::shared_ptr host_api_factory_; }; diff --git a/test/core/runtime/stack_limiter_test.cpp b/test/core/runtime/stack_limiter_test.cpp index 4b33771c46..c94407bd6e 100644 --- a/test/core/runtime/stack_limiter_test.cpp +++ b/test/core/runtime/stack_limiter_test.cpp @@ -9,18 +9,32 @@ #include #include #include +#include #include #include #include #include +#include "common/bytestr.hpp" #include "log/logger.hpp" -#include "runtime/common/stack_limiter.hpp" +#include "runtime/wabt/instrument.hpp" +#include "runtime/wabt/stack_limiter.hpp" +#include "runtime/wabt/util.hpp" #include "testutil/outcome.hpp" #include "testutil/prepare_loggers.hpp" static constexpr uint32_t ACTIVATION_FRAME_COST = 2; +using kagome::byte2str; +using kagome::HeapAllocStrategy; +using kagome::HeapAllocStrategyDynamic; +using kagome::HeapAllocStrategyStatic; +using kagome::str2byte; +using kagome::runtime::convertMemoryImportIntoExport; +using kagome::runtime::setupMemoryAccordingToHeapAllocStrategy; +using kagome::runtime::wabtDecode; +using kagome::runtime::wabtEncode; + std::unique_ptr wat_to_module(std::span wat) { wabt::Result result; wabt::Errors errors; @@ -52,9 +66,26 @@ std::vector wat_to_wasm(std::span wat) { return std::move(stream.output_buffer().data); } +auto fromWat(std::string_view wat) { + return wat_to_module(str2byte(wat)); +} + +std::string toWat(const wabt::Module &module) { + wabt::MemoryStream s; + EXPECT_TRUE( + wabt::Succeeded(wabt::WriteWat(&s, &module, wabt::WriteWatOptions{}))); + return std::string{byte2str(s.output_buffer().data)}; +} + +void expectWasm(const wabt::Module &actual, std::string_view expected) { + auto expected_fmt = toWat(*fromWat(expected)); + EXPECT_EQ(toWat(actual), expected_fmt); + auto actual2 = wabtDecode(wabtEncode(actual).value()).value(); + EXPECT_EQ(toWat(actual2), expected_fmt); +} + uint32_t compute_cost(std::string_view data) { - auto module = wat_to_module( - std::span{reinterpret_cast(data.data()), data.size()}); + auto module = fromWat(data); EXPECT_OUTCOME_TRUE(cost, kagome::runtime::detail::compute_stack_cost( kagome::log::createLogger("StackLimiterTest"), @@ -213,15 +244,7 @@ TEST_P(StackLimiterCompareTest, output_matches_expected) { throw std::runtime_error{"Failed to read binary module"}; } - wabt::MemoryStream result_stream; - wabt::WriteWat(&result_stream, &result_module, wabt::WriteWatOptions{}); - - wabt::MemoryStream expected_stream; - wabt::WriteWat( - &expected_stream, expected_module.get(), wabt::WriteWatOptions{}); - - if (result_stream.output_buffer().data - != expected_stream.output_buffer().data) { + if (toWat(result_module) != toWat(*expected_module)) { std::filesystem::create_directories(std::filesystem::temp_directory_path() / "kagome_test"); auto base_path = std::filesystem::temp_directory_path() / "kagome_test"; @@ -250,3 +273,52 @@ INSTANTIATE_TEST_SUITE_P(SuiteFromSubstrate, "simple", "start", "table")); + +auto wat_memory_import = R"( + (module + (import "env" "mem" (memory (;0;) 100))) +)"; +auto wat_memory_export = R"( + (module + (memory (;0;) 100) + (export "mem" (memory 0))) +)"; +auto memory_limit_static = std::make_pair(HeapAllocStrategyStatic{100}, R"( + (module + (memory (;0;) 200 200) + (export "mem" (memory 0))) +)"); + +/// Check `convertMemoryImportIntoExport`. +TEST(WasmInstrumentTest, memory_import) { + auto module = fromWat(wat_memory_import); + convertMemoryImportIntoExport(*module).value(); + expectWasm(*module, wat_memory_export); +} + +/// Check `setupMemoryAccordingToHeapAllocStrategy`. +TEST(WasmInstrumentTest, memory_limit) { + auto test = [](HeapAllocStrategy config, std::string_view expected) { + auto module = fromWat(wat_memory_export); + setupMemoryAccordingToHeapAllocStrategy(*module, config).value(); + expectWasm(*module, expected); + }; + test(HeapAllocStrategyDynamic{}, wat_memory_export); + test(HeapAllocStrategyDynamic{200}, R"( + (module + (memory (;0;) 100 200) + (export "mem" (memory 0))) + )"); + test(memory_limit_static.first, memory_limit_static.second); +} + +/// Check both `convertMemoryImportIntoExport` and +/// `setupMemoryAccordingToHeapAllocStrategy` to check wabt encoding +/// consistency. +TEST(WasmInstrumentTest, memory_import_limit) { + auto module = fromWat(wat_memory_import); + convertMemoryImportIntoExport(*module).value(); + setupMemoryAccordingToHeapAllocStrategy(*module, memory_limit_static.first) + .value(); + expectWasm(*module, memory_limit_static.second); +} diff --git a/test/core/runtime/storage_code_provider_test.cpp b/test/core/runtime/storage_code_provider_test.cpp index 34a14e61e8..b644f4c65c 100644 --- a/test/core/runtime/storage_code_provider_test.cpp +++ b/test/core/runtime/storage_code_provider_test.cpp @@ -73,7 +73,7 @@ TEST_F(StorageCodeProviderTest, GetCodeWhenNoStorageUpdates) { wasm_provider->getCodeAt(first_state_root)); // then - ASSERT_TRUE(obtained_state_code == common::BufferView(state_code_)); + EXPECT_EQ(*obtained_state_code, state_code_); } /** @@ -120,5 +120,5 @@ TEST_F(StorageCodeProviderTest, DISABLED_GetCodeWhenStorageUpdates) { wasm_provider->getCodeAt(second_state_root)); // then - ASSERT_EQ(obtained_state_code, common::BufferView(state_code_)); + ASSERT_EQ(*obtained_state_code, state_code_); } diff --git a/test/core/runtime/wavm/wavm_module_init_test.cpp b/test/core/runtime/wavm/wavm_module_init_test.cpp index a73b37d10f..ed6773d752 100644 --- a/test/core/runtime/wavm/wavm_module_init_test.cpp +++ b/test/core/runtime/wavm/wavm_module_init_test.cpp @@ -134,7 +134,6 @@ class WavmModuleInitTest : public ::testing::TestWithParam { offchain_persistent_storage, offchain_worker_pool); - auto smc = std::make_shared(); auto cache = std::make_shared(); diff --git a/test/core/runtime/wavm/wavm_runtime_test.hpp b/test/core/runtime/wavm/wavm_runtime_test.hpp index 959be259bd..8cd61ae8c6 100644 --- a/test/core/runtime/wavm/wavm_runtime_test.hpp +++ b/test/core/runtime/wavm/wavm_runtime_test.hpp @@ -49,7 +49,6 @@ class WavmRuntimeTest : public RuntimeTestBase { trie_storage_, serializer_, intrinsic_module, - std::make_shared(), std::nullopt, hasher_); diff --git a/test/external-project-test/src/main.cpp b/test/external-project-test/src/main.cpp index 80a496e19b..0c6af20345 100644 --- a/test/external-project-test/src/main.cpp +++ b/test/external-project-test/src/main.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -145,13 +146,24 @@ int main() { auto bip39_provider = std::make_shared( pbkdf2_provider, hasher); + auto key_store_dir = "/tmp/kagome_tmp_key_storage"; std::shared_ptr key_fs = - kagome::crypto::KeyFileStorage::createAt("/tmp/kagome_tmp_key_storage") - .value(); + kagome::crypto::KeyFileStorage::createAt(key_store_dir).value(); auto csprng = std::make_shared(); - auto crypto_store = - std::make_shared(ecdsa_suite, ed25519_provider); + auto crypto_store = std::make_shared( + std::make_unique< + kagome::crypto::KeySuiteStoreImpl>( + sr25519_provider, bip39_provider, csprng, key_fs), + std::make_unique< + kagome::crypto::KeySuiteStoreImpl>( + ed25519_provider, bip39_provider, csprng, key_fs), + std::make_unique< + kagome::crypto::KeySuiteStoreImpl>( + ecdsa_provider, bip39_provider, csprng, key_fs), + ed25519_provider, + app_state_manager, + kagome::crypto::KeyStore::Config{key_store_dir}); auto offchain_persistent_storage = std::make_shared( @@ -174,8 +186,6 @@ int main() { auto cache = std::make_shared(); - auto smc = std::make_shared(); - auto instance_env_factory = std::make_shared( trie_storage, serializer, host_api_factory); @@ -189,9 +199,10 @@ int main() { module_factory, std::make_shared()); auto module_repo = std::make_shared( runtime_instances_pool, + hasher, runtime_upgrade_tracker, + trie_storage, module_factory, - smc, code_provider); [[maybe_unused]] auto ctx_factory = diff --git a/test/mock/core/runtime/instrument_wasm.hpp b/test/mock/core/runtime/instrument_wasm.hpp index 3ceb6e9019..cb297f39a4 100644 --- a/test/mock/core/runtime/instrument_wasm.hpp +++ b/test/mock/core/runtime/instrument_wasm.hpp @@ -6,7 +6,7 @@ #pragma once -#include "runtime/common/stack_limiter.hpp" +#include "runtime/wabt/instrument.hpp" #include diff --git a/test/mock/core/runtime/runtime_instances_pool_mock.hpp b/test/mock/core/runtime/runtime_instances_pool_mock.hpp index e1f5d47637..d60268b47d 100644 --- a/test/mock/core/runtime/runtime_instances_pool_mock.hpp +++ b/test/mock/core/runtime/runtime_instances_pool_mock.hpp @@ -20,23 +20,10 @@ namespace kagome::runtime { common::BufferView code_zstd, const RuntimeContext::ContextParams &config)); - MOCK_METHOD(outcome::result>, - instantiateFromState, - (const TrieHash &state, - const RuntimeContext::ContextParams &config)); - MOCK_METHOD(void, release, (const TrieHash &state, std::shared_ptr &&instance)); - - MOCK_METHOD(std::optional>, - getModule, - (const TrieHash &state)); - - MOCK_METHOD(void, - putModule, - (const TrieHash &state, std::shared_ptr module)); }; } // namespace kagome::runtime diff --git a/test/testutil/runtime/common/basic_code_provider.cpp b/test/testutil/runtime/common/basic_code_provider.cpp index 765ea4e515..8e80c3cff7 100644 --- a/test/testutil/runtime/common/basic_code_provider.cpp +++ b/test/testutil/runtime/common/basic_code_provider.cpp @@ -9,21 +9,21 @@ #include "utils/read_file.hpp" namespace kagome::runtime { - using kagome::common::Buffer; - BasicCodeProvider::BasicCodeProvider(std::string_view path) { initialize(path); } - outcome::result BasicCodeProvider::getCodeAt( + RuntimeCodeProvider::Result BasicCodeProvider::getCodeAt( const storage::trie::RootHash &at) const { return buffer_; } void BasicCodeProvider::initialize(std::string_view path) { - if (not readFile(buffer_, std::string{path})) { + common::Buffer code; + if (not readFile(code, std::string{path})) { throw std::runtime_error("File with test code " + std::string(path) + " not found"); } + buffer_ = std::make_shared(std::move(code)); } } // namespace kagome::runtime diff --git a/test/testutil/runtime/common/basic_code_provider.hpp b/test/testutil/runtime/common/basic_code_provider.hpp index 2b747b3897..2f458c4f00 100644 --- a/test/testutil/runtime/common/basic_code_provider.hpp +++ b/test/testutil/runtime/common/basic_code_provider.hpp @@ -14,15 +14,12 @@ namespace kagome::runtime { public: explicit BasicCodeProvider(std::string_view path); - ~BasicCodeProvider() override = default; - - outcome::result getCodeAt( - const storage::trie::RootHash &state) const override; + Result getCodeAt(const storage::trie::RootHash &state) const override; private: void initialize(std::string_view path); - kagome::common::Buffer buffer_; + Code buffer_; }; } // namespace kagome::runtime diff --git a/test/testutil/runtime/memory.hpp b/test/testutil/runtime/memory.hpp index ee38ce1f72..e88210ac64 100644 --- a/test/testutil/runtime/memory.hpp +++ b/test/testutil/runtime/memory.hpp @@ -14,11 +14,16 @@ namespace kagome::runtime { struct TestMemory : Memory { mutable common::Buffer m; + std::optional pages_max; WasmSize size() const override { return m.size(); } + std::optional pagesMax() const override { + return pages_max; + } + void resize(WasmSize size) override { m.resize(size); }