diff --git a/core/network/helpers/scale_message_read_writer.hpp b/core/network/helpers/scale_message_read_writer.hpp index d9a320227a..8432a580e8 100644 --- a/core/network/helpers/scale_message_read_writer.hpp +++ b/core/network/helpers/scale_message_read_writer.hpp @@ -49,7 +49,7 @@ namespace kagome::network { static auto empty = std::make_shared>(); raw = empty; } - auto msg_res = scale::decode(*raw); + auto msg_res = ::scale::decode(*raw); if (!msg_res) { return cb(outcome::failure(msg_res.error())); } diff --git a/core/primitives/math.hpp b/core/primitives/math.hpp index 7955e42f8b..95a79f86b5 100644 --- a/core/primitives/math.hpp +++ b/core/primitives/math.hpp @@ -35,6 +35,22 @@ namespace kagome::math { return res; } + template >, bool> = true> + constexpr auto toLE(const T &value) { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + constexpr size_t size = sizeof(std::decay_t); + if constexpr (size == 8) { + return __builtin_bswap64(value); + } else if constexpr (size == 4) { + return __builtin_bswap32(value); + } else if constexpr (size == 2) { + return __builtin_bswap16(value); + } +#endif + return value; + } + inline bool isPowerOf2(size_t x) { return ((x > 0ull) && ((x & (x - 1ull)) == 0)); } @@ -46,4 +62,5 @@ namespace kagome::math { const auto p = k == 0ull ? 0ull : 64ull - __builtin_clzll(k); return (1ull << p); } + } // namespace kagome::math diff --git a/core/scale/encoder/primitives.hpp b/core/scale/encoder/primitives.hpp new file mode 100644 index 0000000000..c8650ed335 --- /dev/null +++ b/core/scale/encoder/primitives.hpp @@ -0,0 +1,462 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_SCALE_ENCODER_PRIMITIVES_HPP +#define KAGOME_SCALE_ENCODER_PRIMITIVES_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils/struct_to_tuple.hpp" + +namespace kagome::scale { + + template + constexpr void putByte(const F &func, const uint8_t *const val, size_t count); + + template + constexpr void encode(const F &func, const std::tuple &v); + + template + constexpr void encode(const F &func, const T &t, const Args &...args); + + template + constexpr void encode(const F &func, const std::vector &c); + + template + constexpr void encode(const FN &func, const std::pair &p); + + template + constexpr void encode(const F &func, const std::span &c); + + template + constexpr void encode(const F &func, const std::span &c); + + template + constexpr void encode(const F &func, const std::array &c); + + template + constexpr void encode(const F &func, const T (&c)[N]); + + template + constexpr void encode(const F &func, const std::map &c); + + template + constexpr void encode(const F &func, const std::shared_ptr &v); + + template + constexpr void encode(const F &func, const std::string_view &v); + + template + constexpr void encode(const F &func, const std::string &v); + + template + constexpr void encode(const F &func, const std::unique_ptr &v); + + template + constexpr void encode(const F &func, const std::list &c); + + template + constexpr void encode(const F &func, const std::deque &c); + + template + void encode(const F &func, const boost::variant &v); + + template + void encode(const F &func, const boost::variant &v); + + template + void encode(const F &func, const ::scale::CompactInteger &value); + + template + void encode(const F &func, const ::scale::BitVec &value); + + template + void encode(const F &func, const std::optional &value); + + template + void encode(const F &func, const std::optional &value); + + template >, bool> = true> + constexpr void encode(const F &func, const T &v) { + using I = std::decay_t; + if constexpr (std::is_integral_v) { + if constexpr (std::is_same_v) { + const uint8_t byte = (v ? 1u : 0u); + putByte(func, &byte, 1ul); + return; + } + + if constexpr (sizeof(I) == 1u) { + putByte(func, (const uint8_t *)&v, size_t(1ull)); + return; + } + + constexpr size_t size = sizeof(I); + const auto val = math::toLE(v); + putByte(func, (uint8_t *)&val, size); + } else { + encode(func, utils::to_tuple_refs(v)); + } + } + + template >, bool> = true> + constexpr void encode(const F &func, const T &value) { + encode(func, static_cast>>(value)); + } + + template + constexpr void encode(const F &func, const T &t, const Args &...args) { + encode(func, t); + encode(func, args...); + } + + template + outcome::result> encode(const Args &...args) { + std::vector res; + encode( + [&](const uint8_t *const val, size_t count) { + if (count != 0ull) { + res.insert(res.end(), &val[0], &val[count]); + } + }, + args...); + return res; + } + + inline size_t bitUpperBorder(const ::scale::CompactInteger &x) { + namespace mp = boost::multiprecision; + const size_t size = x.backend().size(); + const mp::limb_type *const p = x.backend().limbs(); + + auto counter = (size - 1) * sizeof(mp::limb_type) * 8; + auto value = p[size - 1]; + + if constexpr (sizeof(mp::limb_type) == sizeof(uint64_t)) { + counter += sizeof(uint64_t) * 8 + - (value == 0 ? sizeof(uint64_t) * 8 : __builtin_clzll(value)); + } else if constexpr (sizeof(mp::limb_type) == sizeof(uint32_t)) { + counter += sizeof(uint32_t) * 8 + - (value == 0 ? sizeof(uint32_t) * 8 : __builtin_clz(value)); + } + return counter; + } + + inline size_t countBytes(::scale::CompactInteger x) { + if (x == 0) { + return 1ull; + } + namespace mp = boost::multiprecision; + const size_t size = x.backend().size(); + const mp::limb_type *const p = x.backend().limbs(); + + auto counter = (size - 1) * sizeof(mp::limb_type); + auto value = p[size - 1]; + + static_assert(sizeof(mp::limb_type) >= sizeof(uint32_t), + "Unexpected limb size"); + if constexpr (sizeof(mp::limb_type) == sizeof(uint64_t)) { + counter += 1 + ((sizeof(uint64_t) * 8 - __builtin_clzll(value)) - 1) / 8; + } else if constexpr (sizeof(mp::limb_type) == sizeof(uint32_t)) { + counter += 1 + ((sizeof(uint32_t) * 8 - __builtin_clz(value)) - 1) / 8; + } + return counter; + } + + template + constexpr void putByte(const F &func, + const uint8_t *const val, + size_t count) { + func(val, count); + } + + template + void encode(const F &func, const boost::variant &v) { + using T = std::tuple_element_t>; + if (v.which() == I) { + encode(func, I); + encode(func, boost::get(v)); + return; + } + if constexpr (sizeof...(Ts) > I + 1) { + encode(func, v); + } + } + + template + constexpr void encode(const F &func, const T (&c)[N]) { + using E = std::decay_t; + if constexpr (std::is_integral_v && sizeof(E) == 1u) { + putByte(func, c, N); + } else { + for (const auto &e : c) { + encode(func, e); + } + } + } + + template + void encode(const F &func, const boost::variant &v) { + encode(func, v); + } + + template , + std::enable_if_t, bool> = true> + constexpr void encodeCompactSmall(const F &func, T val) { + BOOST_ASSERT_MSG((val >> (8 * sizeof(I) - 2)) == 0, + "Unexpected compact value in encoder"); + val <<= 2; + val |= (sizeof(I) / 2ull); + + encode(func, val); + } + + template + void encodeCompact(const F &func, uint64_t val) { + if (val < ::scale::compact::EncodingCategoryLimits::kMinUint16) { + encodeCompactSmall(func, static_cast(val)); + return; + } + + if (val < ::scale::compact::EncodingCategoryLimits::kMinUint32) { + encodeCompactSmall(func, static_cast(val)); + return; + } + + if (val < ::scale::compact::EncodingCategoryLimits::kMinBigInteger) { + encodeCompactSmall(func, static_cast(val)); + return; + } + + const size_t bigIntLength = sizeof(uint64_t) - (__builtin_clzll(val) / 8); + + uint8_t result[sizeof(uint64_t) + sizeof(uint8_t)]; + result[0] = (bigIntLength - 4) * 4 + 3; // header + + *(uint64_t *)&result[1] = math::toLE(val); + putByte(func, result, bigIntLength + 1ull); + } + + template + constexpr void encode(const F &func, const std::string &v) { + encode(func, std::string_view{v}); + } + + template + constexpr void encode(const F &func, const std::string_view &v) { + encodeCompact(func, v.size()); + putByte(func, (const uint8_t *)v.data(), v.size()); + } + + template + void encode(const F &func, const ::scale::BitVec &v) { + const size_t bitsCount = v.bits.size(); + const size_t bytesCount = ((bitsCount + 7ull) >> 3ull); + const size_t blocksCount = ((bytesCount + 7ull) >> 3ull); + + encodeCompact(func, bitsCount); + uint64_t result; + size_t bitCounter = 0ull; + for (size_t ix = 0ull; ix < blocksCount; ++ix) { + result = 0ull; + size_t remains = std::min(size_t(64ull), bitsCount - bitCounter); + do { + result |= ((v.bits[bitCounter] ? 1ull : 0ull) << (bitCounter % 64ull)); + ++bitCounter; + } while (--remains); + + const size_t bits = (bitCounter % 64ull); + const size_t bytes = + (bits != 0ull) ? ((bits + 7ull) >> 3ull) : sizeof(result); + + result = math::toLE(result); + putByte(func, (uint8_t *)&result, bytes); + } + } + + template + void encode(const F &func, const ::scale::CompactInteger &value) { + if (value < 0) { + raise(::scale::EncodeError::NEGATIVE_COMPACT_INTEGER); + } + + const size_t bit_border = bitUpperBorder(value); + if (bit_border <= 6) { // kMinUint16 + encodeCompactSmall(func, value.convert_to()); + return; + } + + if (bit_border <= 14) { // kMinUint32 + encodeCompactSmall(func, value.convert_to()); + return; + } + + if (bit_border <= 30) { // kMinBigInteger + encodeCompactSmall(func, value.convert_to()); + return; + } + + constexpr size_t kReserved = 68ull; + const size_t bigIntLength = countBytes(value); + + if (bigIntLength >= kReserved) { + raise(::scale::EncodeError::COMPACT_INTEGER_TOO_BIG); + } + + namespace mp = boost::multiprecision; + constexpr size_t limb_sz = sizeof(mp::limb_type); + + uint8_t result[1 + ((kReserved + limb_sz - 1) / limb_sz) * limb_sz]; + result[0] = (bigIntLength - 4) * 4 + 3; // header + + const size_t size = value.backend().size(); + const mp::limb_type *const p = value.backend().limbs(); + + size_t ix = 0ull; + for (; ix < size; ++ix) { + *(mp::limb_type *)&result[1ull + ix * limb_sz] = math::toLE(p[ix]); + } + + putByte(func, result, bigIntLength + 1ull); + } + + template < + typename F, + typename It, + typename = std::enable_if_t< + !std::is_same_v::value_type, void>>> + constexpr void encode(const F &func, It begin, It end) { + while (begin != end) { + encode(func, *begin); + ++begin; + } + } + + template + constexpr void encode(const F &func, const std::span &c) { + encodeCompact(func, c.size()); + encode(func, c.begin(), c.end()); + } + + template + constexpr void encode(const F &func, const std::span &c) { + if constexpr (S == -1) { + encodeCompact(func, c.size()); + encode(func, c.begin(), c.end()); + } else { + using E = std::decay_t; + if constexpr (std::is_integral_v && sizeof(E) == 1u) { + putByte(func, c.data(), c.size()); + } else { + for (const auto &e : c) { + encode(func, e); + } + } + } + } + + template + constexpr void encode(const F &func, const std::array &c) { + for (const auto &e : c) { + encode(func, e); + } + } + + template + constexpr void encode(const F &func, const std::map &c) { + encodeCompact(func, c.size()); + encode(func, c.begin(), c.end()); + } + + template + constexpr void encode(const FN &func, const std::pair &p) { + encode(func, p.first); + encode(func, p.second); + } + + template + constexpr void encode(const F &func, const std::vector &c) { + encodeCompact(func, c.size()); + encode(func, c.begin(), c.end()); + } + + template + constexpr void encode(const F &func, const std::shared_ptr &v) { + if (v == nullptr) { + raise(::scale::EncodeError::DEREF_NULLPOINTER); + } + encode(func, *v); + } + + template + constexpr void encode(const F &func, const std::unique_ptr &v) { + if (v == nullptr) { + raise(::scale::EncodeError::DEREF_NULLPOINTER); + } + encode(func, *v); + } + + template + constexpr void encode(const F &func, const std::list &c) { + encodeCompact(func, c.size()); + encode(func, c.begin(), c.end()); + } + + template + constexpr void encode(const F &func, const std::deque &c) { + encodeCompact(func, c.size()); + encode(func, c.begin(), c.end()); + } + + template + constexpr void encode(const F &func, const std::tuple &v) { + if constexpr (sizeof...(Ts) > 0) { + std::apply([&](const auto &...s) { (..., encode(func, s)); }, v); + } + } + + template + void encode(const F &func, const std::optional &v) { + enum class OptionalBool : uint8_t { + NONE = 0u, + OPT_TRUE = 1u, + OPT_FALSE = 2u + }; + + auto result = OptionalBool::OPT_TRUE; + if (!v.has_value()) { + result = OptionalBool::NONE; + } else if (!*v) { + result = OptionalBool::OPT_FALSE; + } + encode(func, result); + } + + template + void encode(const F &func, const std::optional &v) { + if (!v.has_value()) { + encode(func, uint8_t(0u)); + } else { + encode(func, uint8_t(1u)); + encode(func, *v); + } + } + +} // namespace kagome::scale + +#endif // KAGOME_SCALE_ENCODER_PRIMITIVES_HPP diff --git a/core/scale/kagome_scale.hpp b/core/scale/kagome_scale.hpp new file mode 100644 index 0000000000..0bdbc36c2a --- /dev/null +++ b/core/scale/kagome_scale.hpp @@ -0,0 +1,144 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_KAGOME_SCALE_HPP +#define KAGOME_KAGOME_SCALE_HPP + +#include +#include "common/blob.hpp" +#include "consensus/babe/types/babe_block_header.hpp" +#include "consensus/babe/types/seal.hpp" +#include "network/types/blocks_response.hpp" +#include "primitives/block_header.hpp" +#include "primitives/block_id.hpp" +#include "primitives/justification.hpp" +#include "scale/encode_append.hpp" + +namespace kagome::scale { + using CompactInteger = ::scale::CompactInteger; + + template + constexpr void encode(const F &func, const primitives::BlockHeader &bh); + + template + constexpr void encode(const F &func, const network::BlocksResponse &b); + + template + constexpr void encode( + const F &func, const common::SLVector &c); + + template + constexpr void encode(const F &func, const Tagged &c); + + template + constexpr void encode(const F &func, const common::SLBuffer &c); + + template + constexpr void encode(const F &func, const primitives::Other &c); + + template + constexpr void encode(const F &func, const primitives::Consensus &c); + + template + constexpr void encode(const F &func, const primitives::Seal &c); + + template + constexpr void encode(const F &func, const primitives::PreRuntime &c); + + template + constexpr void encode(const F &func, + const primitives::RuntimeEnvironmentUpdated &c); + + template + constexpr void encode(const F &func, const ::scale::EncodeOpaqueValue &c); + + template + constexpr void encode(const F &func, + const consensus::babe::BabeBlockHeader &bh); + +} // namespace kagome::scale + +#include "scale/encoder/primitives.hpp" + +namespace kagome::scale { + + template + constexpr void encode(const F &func, const primitives::BlockHeader &bh) { + encode(func, bh.parent_hash); + encodeCompact(func, bh.number); + encode(func, bh.state_root); + encode(func, bh.extrinsics_root); + encode(func, bh.digest); + } + + template + constexpr void encode(const F &func, const network::BlocksResponse &b) { + encode(func, b.blocks); + } + + template + constexpr void encode(const F &func, + const consensus::babe::BabeBlockHeader &bh) { + encode(func, bh.slot_assignment_type); + encode(func, bh.authority_index); + encode(func, bh.slot_number); + + if (bh.needVRFCheck()) { + encode(func, bh.vrf_output); + } + } + + template + constexpr void encode( + const F &func, const common::SLVector &c) { + encode(func, static_cast &>(c)); + } + + template + constexpr void encode(const F &func, const Tagged &c) { + if constexpr (std::is_scalar_v) { + encode(func, c.Wrapper::value); + } else { + encode(func, static_cast(c)); + } + } + + template + constexpr void encode(const F &func, const common::SLBuffer &c) { + encode(func, static_cast &>(c)); + } + + template + constexpr void encode(const F &func, const primitives::Other &c) { + encode(func, static_cast(c)); + } + + template + constexpr void encode(const F &func, const primitives::Consensus &c) { + encode(func, static_cast(c)); + } + + template + constexpr void encode(const F &func, const primitives::Seal &c) { + encode(func, static_cast(c)); + } + + template + constexpr void encode(const F &func, const primitives::PreRuntime &c) { + encode(func, static_cast(c)); + } + + template + constexpr void encode(const F &func, + const primitives::RuntimeEnvironmentUpdated &c) {} + + template + constexpr void encode(const F &func, const ::scale::EncodeOpaqueValue &c) { + putByte(func, c.v.data(), c.v.size()); + } + +} // namespace kagome::scale + +#endif // KAGOME_KAGOME_SCALE_HPP diff --git a/core/utils/struct_to_tuple.hpp b/core/utils/struct_to_tuple.hpp new file mode 100644 index 0000000000..51df18ed49 --- /dev/null +++ b/core/utils/struct_to_tuple.hpp @@ -0,0 +1,145 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_STRUCT_TO_TUPLE_HPP +#define KAGOME_STRUCT_TO_TUPLE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#define REP0(X) +#define REP1(X) X +#define REP2(X) REP1(X), X +#define REP3(X) REP2(X), X +#define REP4(X) REP3(X), X +#define REP5(X) REP4(X), X +#define REP6(X) REP5(X), X +#define REP7(X) REP6(X), X +#define REP8(X) REP7(X), X +#define REP9(X) REP8(X), X +#define REP10(X) REP9(X), X + +#define REP0Y(X) +#define REP1Y(X) X##1 +#define REP2Y(X) REP1Y(X), X##2 +#define REP3Y(X) REP2Y(X), X##3 +#define REP4Y(X) REP3Y(X), X##4 +#define REP5Y(X) REP4Y(X), X##5 +#define REP6Y(X) REP5Y(X), X##6 +#define REP7Y(X) REP6Y(X), X##7 +#define REP8Y(X) REP7Y(X), X##8 +#define REP9Y(X) REP8Y(X), X##9 + +#define REP0Y_REF(X) +#define REP1Y_REF(X) std::cref(X##1) +#define REP2Y_REF(X) REP1Y_REF(X), std::cref(X##2) +#define REP3Y_REF(X) REP2Y_REF(X), std::cref(X##3) +#define REP4Y_REF(X) REP3Y_REF(X), std::cref(X##4) +#define REP5Y_REF(X) REP4Y_REF(X), std::cref(X##5) +#define REP6Y_REF(X) REP5Y_REF(X), std::cref(X##6) +#define REP7Y_REF(X) REP6Y_REF(X), std::cref(X##7) +#define REP8Y_REF(X) REP7Y_REF(X), std::cref(X##8) +#define REP9Y_REF(X) REP8Y_REF(X), std::cref(X##9) + +#define REPEAT(TENS, ONES, X) REP##TENS(REP10(X)) REP##ONES(X) +#define REPEATY(ONES, X) REP##ONES##Y(X) +#define REPEATY_REF(ONES, X) REP##ONES##Y_REF(X) + +#define TO_TUPLE_N(ONES) \ + if constexpr (is_braces_constructible{}) { \ + const auto &[REPEATY(ONES, p)] = object; \ + return std::make_tuple(REPEATY_REF(ONES, p)); \ + } + +#define TO_TUPLE1 \ + TO_TUPLE_N(1) else { \ + return std::make_tuple(); \ + } +#define TO_TUPLE2 TO_TUPLE_N(2) else TO_TUPLE1 +#define TO_TUPLE3 TO_TUPLE_N(3) else TO_TUPLE2 +#define TO_TUPLE4 TO_TUPLE_N(4) else TO_TUPLE3 +#define TO_TUPLE5 TO_TUPLE_N(5) else TO_TUPLE4 +#define TO_TUPLE6 TO_TUPLE_N(6) else TO_TUPLE5 +#define TO_TUPLE7 TO_TUPLE_N(7) else TO_TUPLE6 +#define TO_TUPLE8 TO_TUPLE_N(8) else TO_TUPLE7 +#define TO_TUPLE9 TO_TUPLE_N(9) else TO_TUPLE8 + +namespace kagome::utils { + + template + decltype(void(T{{std::declval()}...}), std::true_type{}) + test_is_braces_constructible(int); + + template + std::false_type test_is_braces_constructible(...); + + template + using is_braces_constructible = + decltype(test_is_braces_constructible(0)); + + struct any_type { + template + constexpr operator T(); // non explicit + }; + + template + inline auto to_tuple_refs(const T &object) noexcept { + using type = std::decay_t; + TO_TUPLE9; + } +} // namespace kagome::utils + +#undef REP0 +#undef REP1 +#undef REP2 +#undef REP3 +#undef REP4 +#undef REP5 +#undef REP6 +#undef REP7 +#undef REP8 +#undef REP9 +#undef REP10 +#undef REP0Y +#undef REP1Y +#undef REP2Y +#undef REP3Y +#undef REP4Y +#undef REP5Y +#undef REP6Y +#undef REP7Y +#undef REP8Y +#undef REP9Y +#undef REP0Y_REF +#undef REP1Y_REF +#undef REP2Y_REF +#undef REP3Y_REF +#undef REP4Y_REF +#undef REP5Y_REF +#undef REP6Y_REF +#undef REP7Y_REF +#undef REP8Y_REF +#undef REP9Y_REF +#undef REPEAT +#undef REPEATY +#undef REPEATY_REF +#undef TO_TUPLE_N +#undef TO_TUPLE1 +#undef TO_TUPLE2 +#undef TO_TUPLE3 +#undef TO_TUPLE4 +#undef TO_TUPLE5 +#undef TO_TUPLE6 +#undef TO_TUPLE7 +#undef TO_TUPLE8 +#undef TO_TUPLE9 + +#endif // KAGOME_STRUCT_TO_TUPLE_HPP