diff --git a/core/injector/calculate_genesis_state.hpp b/core/injector/calculate_genesis_state.hpp index 4e010b0543..e2e8ca9515 100644 --- a/core/injector/calculate_genesis_state.hpp +++ b/core/injector/calculate_genesis_state.hpp @@ -7,8 +7,10 @@ #pragma once #include "application/chain_spec.hpp" +#include "runtime/common/uncompress_code_if_needed.hpp" #include "runtime/heap_alloc_strategy_heappages.hpp" #include "runtime/runtime_api/impl/core.hpp" +#include "runtime/wabt/version.hpp" #include "storage/predefined_keys.hpp" #include "storage/trie/polkadot_trie/polkadot_trie_impl.hpp" #include "storage/trie/serialization/trie_serializer.hpp" @@ -30,14 +32,17 @@ namespace kagome::injector { }; auto top_trie = trie_from(chain_spec.getGenesisTopSection()); OUTCOME_TRY(code, top_trie->get(storage::kRuntimeCodeKey)); - - runtime::MemoryLimits config; - BOOST_OUTCOME_TRY(config.heap_alloc_strategy, - heapAllocStrategyHeappagesDefault(*top_trie)); - OUTCOME_TRY( - runtime_version, - runtime::callCoreVersion(module_factory, code, config, runtime_cache)); - auto version = storage::trie::StateVersion{runtime_version.state_version}; + BOOST_OUTCOME_TRY(code, runtime::uncompressCodeIfNeeded(code)); + OUTCOME_TRY(runtime_version, runtime::readEmbeddedVersion(code)); + if (not runtime_version) { + runtime::MemoryLimits config; + BOOST_OUTCOME_TRY(config.heap_alloc_strategy, + heapAllocStrategyHeappagesDefault(*top_trie)); + BOOST_OUTCOME_TRY(runtime_version, + runtime::callCoreVersion( + module_factory, code, config, runtime_cache)); + } + auto version = storage::trie::StateVersion{runtime_version->state_version}; std::vector> child_tries; for (auto &[child, kv] : chain_spec.getGenesisChildrenDefaultSection()) { child_tries.emplace_back(trie_from(kv)); diff --git a/core/primitives/version.cpp b/core/primitives/version.cpp index 069f9d8ee3..68c8cdaa53 100644 --- a/core/primitives/version.cpp +++ b/core/primitives/version.cpp @@ -34,3 +34,35 @@ namespace kagome::primitives::detail { } } // namespace kagome::primitives::detail + +namespace kagome::primitives { + outcome::result Version::decode( + scale::ScaleDecoderStream &s, std::optional core_version) { + Version v; + auto t = std::tie(v.spec_name, + v.impl_name, + v.authoring_version, + v.spec_version, + v.impl_version, + v.apis); + OUTCOME_TRY(scale::decode(s, t)); + + if (not core_version) { + core_version = detail::coreVersionFromApis(v.apis); + } + // old Kusama runtimes do not contain transaction_version and + // state_version + // https://github.com/paritytech/substrate/blob/1b3ddae9dec6e7653b5d6ef0179df1af831f46f0/primitives/version/src/lib.rs#L238 + if (core_version and *core_version >= 3) { + OUTCOME_TRY(scale::decode(s, v.transaction_version)); + } else { + v.transaction_version = 1; + } + if (core_version and *core_version >= 4) { + OUTCOME_TRY(scale::decode(s, v.state_version)); + } else { + v.state_version = 0; + } + return v; + } +} // namespace kagome::primitives diff --git a/core/primitives/version.hpp b/core/primitives/version.hpp index 3c9e335100..f6f0c0cfee 100644 --- a/core/primitives/version.hpp +++ b/core/primitives/version.hpp @@ -91,9 +91,20 @@ namespace kagome::primitives { and state_version == rhs.state_version; } - bool operator!=(const Version &rhs) const { - return !operator==(rhs); - } + /** + * `Decode` while giving a "version hint" + * There exists multiple versions of [`RuntimeVersion`] and they are + * versioned using the `Core` runtime api: + * - `Core` version < 3 is a runtime version without a transaction version + * and state version. + * - `Core` version 3 is a runtime version without a state version. + * - `Core` version 4 is the latest runtime version. + * `core_version` hint is used by `readEmbeddedVersion`, because + * `Version.apis` is stored separately from other `Version` fields. + * https://github.com/paritytech/polkadot-sdk/blob/aaf0443591b134a0da217d575161872796e75059/substrate/primitives/version/src/lib.rs#L242 + */ + static outcome::result decode( + scale::ScaleDecoderStream &s, std::optional core_version); }; namespace detail { @@ -130,24 +141,8 @@ namespace kagome::primitives { template > Stream &operator>>(Stream &s, Version &v) { - s >> v.spec_name >> v.impl_name >> v.authoring_version >> v.spec_version - >> v.impl_version >> v.apis; - - auto core_version = detail::coreVersionFromApis(v.apis); - // old Kusama runtimes do not contain transaction_version and state_version - // https://github.com/paritytech/substrate/blob/1b3ddae9dec6e7653b5d6ef0179df1af831f46f0/primitives/version/src/lib.rs#L238 - if (core_version.has_value() and core_version.value() >= 3 - and s.hasMore(sizeof(v.transaction_version))) { - s >> v.transaction_version; - } else { - v.transaction_version = 1; - } - if (core_version.has_value() and core_version.value() >= 4 - and s.hasMore(sizeof(v.state_version))) { - s >> v.state_version; - } else { - v.state_version = 0; - } + // `.value()` may throw, `scale::decode` will catch that + v = Version::decode(s, std::nullopt).value(); return s; } } // namespace kagome::primitives diff --git a/core/runtime/binaryen/core_api_factory_impl.cpp b/core/runtime/binaryen/core_api_factory_impl.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core/runtime/common/CMakeLists.txt b/core/runtime/common/CMakeLists.txt index 34a13ebc69..3cacfb64f4 100644 --- a/core/runtime/common/CMakeLists.txt +++ b/core/runtime/common/CMakeLists.txt @@ -98,5 +98,7 @@ add_library(core_api_factory core_api_factory_impl.cpp) target_link_libraries(core_api_factory core_api outcome + uncompress_if_needed + wasm_instrument ) kagome_install(core_api_factory) diff --git a/core/runtime/common/core_api_factory_impl.cpp b/core/runtime/common/core_api_factory_impl.cpp index 085659d956..b049781a33 100644 --- a/core/runtime/common/core_api_factory_impl.cpp +++ b/core/runtime/common/core_api_factory_impl.cpp @@ -8,11 +8,25 @@ #include "runtime/common/runtime_properties_cache_impl.hpp" #include "runtime/common/trie_storage_provider_impl.hpp" +#include "runtime/common/uncompress_code_if_needed.hpp" #include "runtime/module_repository.hpp" #include "runtime/runtime_api/impl/core.hpp" #include "runtime/runtime_context.hpp" +#include "runtime/wabt/version.hpp" namespace kagome::runtime { + using primitives::Version; + class GetVersion : public RestrictedCore { + public: + GetVersion(const Version &version) : version_{version} {} + + outcome::result version() { + return version_; + } + + private: + Version version_; + }; CoreApiFactoryImpl::CoreApiFactoryImpl( std::shared_ptr module_factory) @@ -23,6 +37,11 @@ namespace kagome::runtime { outcome::result> CoreApiFactoryImpl::make( std::shared_ptr hasher, const std::vector &runtime_code) const { + OUTCOME_TRY(code, uncompressCodeIfNeeded(runtime_code)); + OUTCOME_TRY(version, readEmbeddedVersion(code)); + if (version) { + return std::make_unique(*version); + } OUTCOME_TRY( ctx, RuntimeContextFactory::fromCode(*module_factory_, runtime_code, {})); diff --git a/core/runtime/common/module_repository_impl.cpp b/core/runtime/common/module_repository_impl.cpp index 89369c11e1..570715018a 100644 --- a/core/runtime/common/module_repository_impl.cpp +++ b/core/runtime/common/module_repository_impl.cpp @@ -6,8 +6,10 @@ #include "runtime/common/module_repository_impl.hpp" +#include "blockchain/block_header_repository.hpp" #include "log/profiling_logger.hpp" #include "runtime/common/runtime_instances_pool.hpp" +#include "runtime/common/uncompress_code_if_needed.hpp" #include "runtime/heap_alloc_strategy_heappages.hpp" #include "runtime/instance_environment.hpp" #include "runtime/module.hpp" @@ -15,6 +17,7 @@ #include "runtime/module_instance.hpp" #include "runtime/runtime_code_provider.hpp" #include "runtime/runtime_upgrade_tracker.hpp" +#include "runtime/wabt/version.hpp" #include "storage/trie/trie_storage.hpp" namespace kagome::runtime { @@ -24,12 +27,15 @@ namespace kagome::runtime { ModuleRepositoryImpl::ModuleRepositoryImpl( std::shared_ptr runtime_instances_pool, std::shared_ptr hasher, + std::shared_ptr + block_header_repository, std::shared_ptr runtime_upgrade_tracker, std::shared_ptr trie_storage, std::shared_ptr module_factory, std::shared_ptr code_provider) : runtime_instances_pool_{std::move(runtime_instances_pool)}, hasher_{std::move(hasher)}, + block_header_repository_{std::move(block_header_repository)}, runtime_upgrade_tracker_{std::move(runtime_upgrade_tracker)}, trie_storage_{std::move(trie_storage)}, module_factory_{std::move(module_factory)}, @@ -45,6 +51,22 @@ namespace kagome::runtime { ModuleRepositoryImpl::getInstanceAt( const primitives::BlockInfo &block, const storage::trie::RootHash &storage_state) { + OUTCOME_TRY(item, codeAt(block, storage_state)); + return runtime_instances_pool_->instantiateFromCode( + item.hash, *item.code, {item.config}); + } + + outcome::result> + ModuleRepositoryImpl::embeddedVersion( + const primitives::BlockHash &block_hash) { + OUTCOME_TRY(header, block_header_repository_->getBlockHeader(block_hash)); + OUTCOME_TRY(item, codeAt(header.blockInfo(), header.state_root)); + return item.version; + } + + outcome::result ModuleRepositoryImpl::codeAt( + const primitives::BlockInfo &block, + const storage::trie::RootHash &storage_state) { KAGOME_PROFILE_START(code_retrieval) OUTCOME_TRY(state, runtime_upgrade_tracker_->getLastCodeUpdateState(block)); KAGOME_PROFILE_END(code_retrieval) @@ -55,12 +77,15 @@ namespace kagome::runtime { 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); + auto code_res = code_provider_->getCodeAt(state); + if (not code_res) { + code_res = code_provider_->getCodeAt(storage_state); } - BOOST_OUTCOME_TRY(item.code, std::move(code)); - item.hash = hasher_->blake2b_256(*item.code); + auto &code_zstd = *code_res.value(); + item.hash = hasher_->blake2b_256(code_zstd); + OUTCOME_TRY(code, uncompressCodeIfNeeded(code_zstd)); + item.code = std::make_shared(code); + BOOST_OUTCOME_TRY(item.version, readEmbeddedVersion(code)); OUTCOME_TRY(batch, trie_storage_->getEphemeralBatchAt(storage_state)); BOOST_OUTCOME_TRY(item.config.heap_alloc_strategy, heapAllocStrategyHeappagesDefault(*batch)); @@ -68,11 +93,6 @@ namespace kagome::runtime { } return outcome::success(); }); - OUTCOME_TRY(runtime_instance, - runtime_instances_pool_->instantiateFromCode( - item.hash, *item.code, {item.config})); - KAGOME_PROFILE_END(module_retrieval) - - return runtime_instance; + return item; } } // namespace kagome::runtime diff --git a/core/runtime/common/module_repository_impl.hpp b/core/runtime/common/module_repository_impl.hpp index d663314e3a..3872b316ec 100644 --- a/core/runtime/common/module_repository_impl.hpp +++ b/core/runtime/common/module_repository_impl.hpp @@ -16,6 +16,10 @@ #include "utils/lru.hpp" #include "utils/safe_object.hpp" +namespace kagome::blockchain { + class BlockHeaderRepository; +} // namespace kagome::blockchain + namespace kagome::crypto { class Hasher; } // namespace kagome::crypto @@ -34,6 +38,8 @@ namespace kagome::runtime { ModuleRepositoryImpl( std::shared_ptr runtime_instances_pool, std::shared_ptr hasher, + std::shared_ptr + block_header_repository, std::shared_ptr runtime_upgrade_tracker, std::shared_ptr trie_storage, std::shared_ptr module_factory, @@ -43,14 +49,23 @@ namespace kagome::runtime { const primitives::BlockInfo &block, const storage::trie::RootHash &state) override; + outcome::result> embeddedVersion( + const primitives::BlockHash &block_hash) override; + private: struct Item { common::Hash256 hash; std::shared_ptr code; + std::optional version; MemoryLimits config; }; + + outcome::result codeAt(const primitives::BlockInfo &block, + const storage::trie::RootHash &storage_state); + std::shared_ptr runtime_instances_pool_; std::shared_ptr hasher_; + std::shared_ptr block_header_repository_; std::shared_ptr runtime_upgrade_tracker_; std::shared_ptr trie_storage_; std::shared_ptr module_factory_; diff --git a/core/runtime/common/uncompress_code_if_needed.hpp b/core/runtime/common/uncompress_code_if_needed.hpp index 3e46085261..338cd9a6d1 100644 --- a/core/runtime/common/uncompress_code_if_needed.hpp +++ b/core/runtime/common/uncompress_code_if_needed.hpp @@ -27,4 +27,11 @@ namespace kagome::runtime { UncompressOutcome uncompressCodeIfNeeded(common::BufferView buf, common::Buffer &res); + + inline UncompressOutcome uncompressCodeIfNeeded( + BufferView data_zstd) { + Buffer data; + OUTCOME_TRY(uncompressCodeIfNeeded(data_zstd, data)); + return data; + } } // namespace kagome::runtime diff --git a/core/runtime/module_repository.hpp b/core/runtime/module_repository.hpp index e76fb5b8b2..31d97c441d 100644 --- a/core/runtime/module_repository.hpp +++ b/core/runtime/module_repository.hpp @@ -13,6 +13,7 @@ #include "host_api/host_api.hpp" #include "outcome/outcome.hpp" #include "primitives/block_data.hpp" +#include "primitives/version.hpp" namespace kagome::runtime { @@ -41,6 +42,12 @@ namespace kagome::runtime { virtual outcome::result> getInstanceAt( const primitives::BlockInfo &block, const storage::trie::RootHash &state_hash) = 0; + + /** + * Return cached `readEmbeddedVersion` result. + */ + virtual outcome::result> embeddedVersion( + const primitives::BlockHash &block_hash) = 0; }; } // namespace kagome::runtime diff --git a/core/runtime/runtime_api/impl/core.cpp b/core/runtime/runtime_api/impl/core.cpp index 7671df1146..7211ab70ad 100644 --- a/core/runtime/runtime_api/impl/core.cpp +++ b/core/runtime/runtime_api/impl/core.cpp @@ -10,6 +10,7 @@ #include "log/logger.hpp" #include "runtime/executor.hpp" #include "runtime/module_instance.hpp" +#include "runtime/module_repository.hpp" #include "runtime/runtime_properties_cache.hpp" namespace kagome::runtime { @@ -44,9 +45,11 @@ namespace kagome::runtime { CoreImpl::CoreImpl( std::shared_ptr executor, + std::shared_ptr module_repository, std::shared_ptr header_repo, std::shared_ptr runtime_upgrade_tracker) : executor_{std::move(executor)}, + module_repository_{std::move(module_repository)}, header_repo_{std::move(header_repo)}, runtime_upgrade_tracker_{std::move(runtime_upgrade_tracker)} { BOOST_ASSERT(executor_ != nullptr); @@ -56,6 +59,10 @@ namespace kagome::runtime { outcome::result CoreImpl::version( const primitives::BlockHash &block) { + OUTCOME_TRY(version, module_repository_->embeddedVersion(block)); + if (version) { + return *version; + } return version_.call(*header_repo_, *runtime_upgrade_tracker_, *executor_, diff --git a/core/runtime/runtime_api/impl/core.hpp b/core/runtime/runtime_api/impl/core.hpp index 20cb844cb5..780c060ee5 100644 --- a/core/runtime/runtime_api/impl/core.hpp +++ b/core/runtime/runtime_api/impl/core.hpp @@ -35,6 +35,7 @@ namespace kagome::runtime { public: CoreImpl( std::shared_ptr executor, + std::shared_ptr module_repository, std::shared_ptr header_repo, std::shared_ptr runtime_upgrade_tracker); @@ -55,6 +56,7 @@ namespace kagome::runtime { private: std::shared_ptr executor_; + std::shared_ptr module_repository_; std::shared_ptr header_repo_; std::shared_ptr runtime_upgrade_tracker_; diff --git a/core/runtime/wabt/CMakeLists.txt b/core/runtime/wabt/CMakeLists.txt index 2e5a248393..7b4f5cfd9c 100644 --- a/core/runtime/wabt/CMakeLists.txt +++ b/core/runtime/wabt/CMakeLists.txt @@ -7,10 +7,12 @@ add_library(wasm_instrument instrument.cpp stack_limiter.cpp + version.cpp ) target_link_libraries(wasm_instrument logger outcome + primitives wabt::wabt ) kagome_install(wasm_instrument) diff --git a/core/runtime/wabt/version.cpp b/core/runtime/wabt/version.cpp new file mode 100644 index 0000000000..37ed002750 --- /dev/null +++ b/core/runtime/wabt/version.cpp @@ -0,0 +1,45 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "runtime/wabt/version.hpp" + +#include "runtime/wabt/util.hpp" + +namespace kagome::runtime { + outcome::result> readEmbeddedVersion( + BufferView wasm) { + OUTCOME_TRY(module, wabtDecode(wasm)); + auto custom_section_contents = [&](std::string_view name) { + auto it = std::find_if( + module.customs.begin(), + module.customs.end(), + [&](const wabt::Custom §ion) { return section.name == name; }); + return it != module.customs.end() + ? std::make_optional(BufferView{it->data}) + : std::nullopt; + }; + auto version_section = custom_section_contents("runtime_version"); + if (not version_section) { + return std::nullopt; + } + std::optional apis; + std::optional core_version; + if (auto apis_section = custom_section_contents("runtime_apis")) { + apis.emplace(); + scale::ScaleDecoderStream s{*apis_section}; + while (s.hasMore(1)) { + OUTCOME_TRY(scale::decode(s, apis->emplace_back())); + } + core_version = primitives::detail::coreVersionFromApis(*apis); + } + scale::ScaleDecoderStream s{*version_section}; + OUTCOME_TRY(version, primitives::Version::decode(s, core_version)); + if (apis) { + version.apis = std::move(*apis); + } + return version; + } +} // namespace kagome::runtime diff --git a/core/runtime/wabt/version.hpp b/core/runtime/wabt/version.hpp new file mode 100644 index 0000000000..faca2d9386 --- /dev/null +++ b/core/runtime/wabt/version.hpp @@ -0,0 +1,21 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "primitives/version.hpp" + +namespace kagome::runtime { + /** + * Take the runtime blob and scan it for the custom wasm sections containing + * the version information and construct the `RuntimeVersion` from them. + * If there are no such sections, it returns `None`. If there is an error + * during decoding those sections, `Err` will be returned. + * https://github.com/paritytech/polkadot-sdk/blob/929a273ae1ba647628c4ba6e2f8737e58b596d6a/substrate/client/executor/src/wasm_runtime.rs#L355 + */ + outcome::result> readEmbeddedVersion( + BufferView wasm); +} // namespace kagome::runtime diff --git a/core/runtime/wavm/core_api_factory_impl.cpp b/core/runtime/wavm/core_api_factory_impl.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/core/runtime/runtime_test_base.hpp b/test/core/runtime/runtime_test_base.hpp index db4437aa3d..47a7737158 100644 --- a/test/core/runtime/runtime_test_base.hpp +++ b/test/core/runtime/runtime_test_base.hpp @@ -171,6 +171,7 @@ class RuntimeTestBase : public ::testing::Test { std::make_shared( module_factory, std::make_shared()), hasher_, + header_repo_, upgrade_tracker, trie_storage_, module_factory, diff --git a/test/core/runtime/wavm/core_integration_test.cpp b/test/core/runtime/wavm/core_integration_test.cpp index a4ef9b7a0b..c49c5983e3 100644 --- a/test/core/runtime/wavm/core_integration_test.cpp +++ b/test/core/runtime/wavm/core_integration_test.cpp @@ -43,7 +43,8 @@ class CoreTest : public WavmRuntimeTest { void SetUp() override { WavmRuntimeTest::SetUp(); - core_ = std::make_shared(executor_, header_repo_, nullptr); + core_ = + std::make_shared(executor_, nullptr, header_repo_, nullptr); } protected: diff --git a/test/external-project-test/src/main.cpp b/test/external-project-test/src/main.cpp index 9615470cd5..7428d62b20 100644 --- a/test/external-project-test/src/main.cpp +++ b/test/external-project-test/src/main.cpp @@ -200,6 +200,7 @@ int main() { auto module_repo = std::make_shared( runtime_instances_pool, hasher, + header_repo, runtime_upgrade_tracker, trie_storage, module_factory, diff --git a/test/mock/core/runtime/module_repository_mock.hpp b/test/mock/core/runtime/module_repository_mock.hpp index f33efd6d75..0d6df6cfc4 100644 --- a/test/mock/core/runtime/module_repository_mock.hpp +++ b/test/mock/core/runtime/module_repository_mock.hpp @@ -21,6 +21,11 @@ namespace kagome::runtime { (const primitives::BlockInfo &block, const storage::trie::RootHash &state_root), (override)); + + MOCK_METHOD(outcome::result>, + embeddedVersion, + (const primitives::BlockHash &), + (override)); }; } // namespace kagome::runtime