diff --git a/core/common/bytestr.hpp b/core/common/bytestr.hpp new file mode 100644 index 0000000000..4010a4533d --- /dev/null +++ b/core/common/bytestr.hpp @@ -0,0 +1,24 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_COMMON_BYTESTR_HPP +#define KAGOME_COMMON_BYTESTR_HPP + +#include +#include +#include + +namespace kagome { + inline gsl::span str2byte(const gsl::span &s) { + return libp2p::bytestr(s); + } + + inline std::string_view byte2str(const gsl::span &s) { + // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + return {reinterpret_cast(s.data()), libp2p::spanSize(s)}; + } +} // namespace kagome + +#endif // KAGOME_COMMON_BYTESTR_HPP diff --git a/core/consensus/babe/impl/babe_impl.cpp b/core/consensus/babe/impl/babe_impl.cpp index 5a1d8f3ac3..5dfaed83a4 100644 --- a/core/consensus/babe/impl/babe_impl.cpp +++ b/core/consensus/babe/impl/babe_impl.cpp @@ -1033,8 +1033,12 @@ namespace kagome::consensus::babe { } // finally, broadcast the sealed block - block_announce_transmitter_->blockAnnounce( - network::BlockAnnounce{block.header}); + block_announce_transmitter_->blockAnnounce(network::BlockAnnounce{ + block.header, + block_info == block_tree_->bestLeaf() ? network::BlockState::Best + : network::BlockState::Normal, + common::Buffer{}, + }); SL_DEBUG( log_, "Announced block number {} in slot {} (epoch {}) with timestamp {}", diff --git a/core/network/CMakeLists.txt b/core/network/CMakeLists.txt index 5bffe158e9..51474ced22 100644 --- a/core/network/CMakeLists.txt +++ b/core/network/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(protobuf) add_library(network + impl/protocols/light.cpp impl/state_protocol_observer_impl.cpp impl/state_sync_request_flow.cpp impl/synchronizer_impl.cpp @@ -34,6 +35,7 @@ add_library(network warp/cache.cpp ) target_link_libraries(network + light_api_proto p2p::p2p_basic_scheduler runtime_environment_factory scale_libp2p_types diff --git a/core/network/adapters/light.hpp b/core/network/adapters/light.hpp new file mode 100644 index 0000000000..6d44e37219 --- /dev/null +++ b/core/network/adapters/light.hpp @@ -0,0 +1,163 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_NETWORK_ADAPTERS_LIGHT_HPP +#define KAGOME_NETWORK_ADAPTERS_LIGHT_HPP + +#include "common/bytestr.hpp" +#include "network/adapters/protobuf.hpp" +#include "network/protobuf/light.v1.pb.h" +#include "primitives/common.hpp" + +namespace kagome::network { + struct LightProtocolRequest { + struct Read { + std::optional child; + std::vector keys; + }; + struct Call { + std::string method; + common::Buffer args; + }; + + primitives::BlockHash block; + boost::variant op; + }; + + struct LightProtocolResponse { + std::vector proof; + bool call = false; + }; + + template <> + struct ProtobufMessageAdapter { + static size_t size(const LightProtocolRequest &t) { + return 0; + } + + static std::vector::iterator write( + const LightProtocolRequest &t, + std::vector &out, + std::vector::iterator loaded) { + ::protobuf_generated::api::v1::light::Request msg; + if (auto call = boost::get(&t.op)) { + auto &pb = *msg.mutable_remote_call_request(); + pb.set_block(std::string{byte2str(t.block)}); + pb.set_method(call->method); + pb.set_data(std::string{byte2str(call->args)}); + } else { + auto &read = boost::get(t.op); + if (read.child) { + auto &pb = *msg.mutable_remote_read_child_request(); + pb.set_block(std::string{byte2str(t.block)}); + pb.set_storage_key(std::string{byte2str(*read.child)}); + for (auto &key : read.keys) { + pb.add_keys(std::string{byte2str(key)}); + } + } else { + auto &pb = *msg.mutable_remote_read_request(); + pb.set_block(std::string{byte2str(t.block)}); + for (auto &key : read.keys) { + pb.add_keys(std::string{byte2str(key)}); + } + } + } + return appendToVec(msg, out, loaded); + } + + static outcome::result::const_iterator> read( + LightProtocolRequest &out, + const std::vector &src, + std::vector::const_iterator from) { + const auto remains = src.size() - std::distance(src.begin(), from); + BOOST_ASSERT(remains >= size(out)); + + ::protobuf_generated::api::v1::light::Request msg; + if (not msg.ParseFromArray(from.base(), remains)) { + return AdaptersError::PARSE_FAILED; + } + + auto get_block = [&](auto &pb) { + auto r = primitives::BlockHash::fromSpan(str2byte(pb.block())); + if (r) { + out.block = r.value(); + } + return r; + }; + if (msg.has_remote_call_request()) { + auto &pb = msg.remote_call_request(); + OUTCOME_TRY(get_block(pb)); + out.op = LightProtocolRequest::Call{ + pb.method(), common::Buffer{str2byte(pb.data())}}; + } else { + LightProtocolRequest::Read read; + auto get_keys = [&](auto &pb) { + for (auto &key : pb.keys()) { + read.keys.emplace_back(str2byte(key)); + } + }; + if (msg.has_remote_read_child_request()) { + auto &pb = msg.remote_read_child_request(); + OUTCOME_TRY(get_block(pb)); + read.child = common::Buffer{str2byte(pb.storage_key())}; + get_keys(pb); + } else { + auto &pb = msg.remote_read_request(); + OUTCOME_TRY(get_block(pb)); + get_keys(pb); + } + out.op = std::move(read); + } + + std::advance(from, msg.ByteSizeLong()); + return from; + } + }; + + template <> + struct ProtobufMessageAdapter { + static size_t size(const LightProtocolResponse &t) { + return 0; + } + + static std::vector::iterator write( + const LightProtocolResponse &t, + std::vector &out, + std::vector::iterator loaded) { + ::protobuf_generated::api::v1::light::Response msg; + auto &proof = t.call + ? *msg.mutable_remote_call_response()->mutable_proof() + : *msg.mutable_remote_read_response()->mutable_proof(); + proof = byte2str(scale::encode(t.proof).value()); + return appendToVec(msg, out, loaded); + } + + static outcome::result::const_iterator> read( + LightProtocolResponse &out, + const std::vector &src, + std::vector::const_iterator from) { + const auto remains = src.size() - std::distance(src.begin(), from); + BOOST_ASSERT(remains >= size(out)); + + ::protobuf_generated::api::v1::light::Response msg; + if (not msg.ParseFromArray(from.base(), remains)) { + return AdaptersError::PARSE_FAILED; + } + + out.call = msg.has_remote_call_response(); + OUTCOME_TRY(proof, + scale::decode>( + str2byte(out.call ? msg.remote_call_response().proof() + : msg.remote_read_response().proof()))); + out.proof = std::move(proof); + + std::advance(from, msg.ByteSizeLong()); + return from; + } + }; + +} // namespace kagome::network + +#endif // KAGOME_NETWORK_ADAPTERS_LIGHT_HPP diff --git a/core/network/adapters/protobuf.hpp b/core/network/adapters/protobuf.hpp index 2326b67bc0..bdf94fb5d9 100644 --- a/core/network/adapters/protobuf.hpp +++ b/core/network/adapters/protobuf.hpp @@ -10,9 +10,9 @@ #include #include #include +#include #include "network/adapters/adapter_errors.hpp" -#include "network/protobuf/api.v1.pb.h" #include "outcome/outcome.hpp" namespace kagome::network { diff --git a/core/network/adapters/protobuf_block_request.hpp b/core/network/adapters/protobuf_block_request.hpp index 608409ce3d..1809f7485c 100644 --- a/core/network/adapters/protobuf_block_request.hpp +++ b/core/network/adapters/protobuf_block_request.hpp @@ -13,6 +13,7 @@ #include "common/visitor.hpp" #include "macro/endianness_utils.hpp" +#include "network/protobuf/api.v1.pb.h" #include "network/types/blocks_request.hpp" namespace kagome::network { diff --git a/core/network/adapters/protobuf_block_response.hpp b/core/network/adapters/protobuf_block_response.hpp index 73b7fa7870..7f3ba12dd1 100644 --- a/core/network/adapters/protobuf_block_response.hpp +++ b/core/network/adapters/protobuf_block_response.hpp @@ -8,6 +8,7 @@ #include "network/adapters/protobuf.hpp" +#include "network/protobuf/api.v1.pb.h" #include "network/types/blocks_response.hpp" #include "scale/scale.hpp" @@ -28,20 +29,25 @@ namespace kagome::network { auto *dst_block = msg.add_blocks(); dst_block->set_hash(src_block.hash.toString()); - if (src_block.header) + if (src_block.header) { dst_block->set_header( vector_to_string(scale::encode(*src_block.header).value())); + } - if (src_block.body) - for (const auto &ext_body : *src_block.body) + if (src_block.body) { + for (const auto &ext_body : *src_block.body) { dst_block->add_body( vector_to_string(scale::encode(ext_body).value())); + } + } - if (src_block.receipt) + if (src_block.receipt) { dst_block->set_receipt(src_block.receipt->toString()); + } - if (src_block.message_queue) + if (src_block.message_queue) { dst_block->set_message_queue(src_block.message_queue->toString()); + } if (src_block.justification) { dst_block->set_justification( @@ -63,8 +69,9 @@ namespace kagome::network { assert(remains >= size(out)); ::api::v1::BlockResponse msg; - if (!msg.ParseFromArray(from.base(), remains)) + if (!msg.ParseFromArray(from.base(), remains)) { return AdaptersError::PARSE_FAILED; + } auto &dst_blocks = out.blocks; dst_blocks.reserve(msg.blocks().size()); @@ -78,7 +85,9 @@ namespace kagome::network { std::optional bodies; for (const auto &b : src_block_data.body()) { - if (!bodies) bodies = primitives::BlockBody{}; + if (!bodies) { + bodies = primitives::BlockBody{}; + } OUTCOME_TRY( body, extract_value([&]() { return b; })); diff --git a/core/network/adapters/protobuf_state_request.hpp b/core/network/adapters/protobuf_state_request.hpp index 9d4c384876..e21c94663c 100644 --- a/core/network/adapters/protobuf_state_request.hpp +++ b/core/network/adapters/protobuf_state_request.hpp @@ -13,6 +13,7 @@ #include "common/visitor.hpp" #include "macro/endianness_utils.hpp" +#include "network/protobuf/api.v1.pb.h" #include "network/types/state_request.hpp" #include "primitives/common.hpp" diff --git a/core/network/adapters/protobuf_state_response.hpp b/core/network/adapters/protobuf_state_response.hpp index 31bc161992..6f2bae7e14 100644 --- a/core/network/adapters/protobuf_state_response.hpp +++ b/core/network/adapters/protobuf_state_response.hpp @@ -8,6 +8,7 @@ #include "network/adapters/protobuf.hpp" +#include "network/protobuf/api.v1.pb.h" #include "network/types/state_response.hpp" #include "scale/scale.hpp" @@ -49,8 +50,9 @@ namespace kagome::network { assert(remains >= size(out)); ::api::v1::StateResponse msg; - if (!msg.ParseFromArray(from.base(), remains)) + if (!msg.ParseFromArray(from.base(), remains)) { return AdaptersError::PARSE_FAILED; + } for (const auto &kvEntry : msg.entries()) { KeyValueStateEntry kv; diff --git a/core/network/common.hpp b/core/network/common.hpp index 3d85d10fee..3629267aad 100644 --- a/core/network/common.hpp +++ b/core/network/common.hpp @@ -26,6 +26,7 @@ namespace kagome::network { "/{}/block-announces/1"; const libp2p::peer::ProtocolName kGrandpaProtocol = "/{}/grandpa/1"; const libp2p::peer::ProtocolName kWarpProtocol = "/{}/sync/warp"; + const libp2p::peer::ProtocolName kLightProtocol = "/{}/light/2"; const libp2p::peer::ProtocolName kCollationProtocol{"/{}/collation/1"}; const libp2p::peer::ProtocolName kValidationProtocol{"/{}/validation/1"}; const libp2p::peer::ProtocolName kReqCollationProtocol{"/{}/req_collation/1"}; diff --git a/core/network/impl/protocols/light.cpp b/core/network/impl/protocols/light.cpp new file mode 100644 index 0000000000..5328f9febb --- /dev/null +++ b/core/network/impl/protocols/light.cpp @@ -0,0 +1,60 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "network/impl/protocols/light.hpp" + +#include "network/common.hpp" +#include "runtime/common/trie_storage_provider_impl.hpp" +#include "runtime/runtime_environment_factory.hpp" + +namespace kagome::network { + LightProtocol::LightProtocol( + libp2p::Host &host, + const application::ChainSpec &chain_spec, + const primitives::GenesisBlockHeader &genesis, + std::shared_ptr repository, + std::shared_ptr storage, + std::shared_ptr env_factory) + : RequestResponseProtocolType{ + kName, + host, + make_protocols(kLightProtocol, chain_spec, genesis), + log::createLogger(kName), + }, + repository_{std::move(repository)}, + storage_{std::move(storage)}, + env_factory_{std::move(env_factory)} {} + + outcome::result LightProtocol::onRxRequest( + RequestType req, std::shared_ptr) { + std::unordered_set proof; + auto prove = [&](common::BufferView raw) { proof.emplace(raw); }; + OUTCOME_TRY(header, repository_->getBlockHeader(req.block)); + OUTCOME_TRY(batch, + storage_->getProofReaderBatchAt(header.state_root, prove)); + auto call = boost::get(&req.op); + if (call) { + OUTCOME_TRY(factory, env_factory_->start(req.block)); + OUTCOME_TRY(env, factory->withStorageBatch(std::move(batch)).make()); + OUTCOME_TRY( + env->module_instance->callExportFunction(call->method, call->args)); + OUTCOME_TRY(env->module_instance->resetEnvironment()); + } else { + auto &read = boost::get(req.op); + runtime::TrieStorageProviderImpl provider{storage_, nullptr}; + provider.setTo(std::move(batch)); + std::reference_wrapper trie = + *provider.getCurrentBatch(); + if (read.child) { + OUTCOME_TRY(child, provider.getChildBatchAt(*read.child)); + trie = child; + } + for (auto &key : read.keys) { + OUTCOME_TRY(trie.get().tryGet(key)); + } + } + return LightProtocolResponse{{proof.begin(), proof.end()}, call != nullptr}; + } +} // namespace kagome::network diff --git a/core/network/impl/protocols/light.hpp b/core/network/impl/protocols/light.hpp new file mode 100644 index 0000000000..7f2b764a7e --- /dev/null +++ b/core/network/impl/protocols/light.hpp @@ -0,0 +1,64 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef KAGOME_NETWORK_IMPL_PROTOCOLS_LIGHT_HPP +#define KAGOME_NETWORK_IMPL_PROTOCOLS_LIGHT_HPP + +#include "network/adapters/light.hpp" +#include "network/helpers/protobuf_message_read_writer.hpp" +#include "network/impl/protocols/request_response_protocol.hpp" + +namespace kagome::application { + class ChainSpec; +} // namespace kagome::application + +namespace kagome::primitives { + struct GenesisBlockHeader; +} // namespace kagome::primitives + +namespace kagome::runtime { + class RuntimeEnvironmentFactory; +} // namespace kagome::runtime + +namespace kagome::storage::trie { + class TrieStorage; +} // namespace kagome::storage::trie + +namespace kagome::blockchain { + class BlockHeaderRepository; +} // namespace kagome::blockchain + +namespace kagome::network { + /** + * https://github.com/paritytech/substrate/tree/master/client/network/light + */ + class LightProtocol + : public RequestResponseProtocol { + static constexpr auto kName = "LightProtocol"; + + public: + LightProtocol( + libp2p::Host &host, + const application::ChainSpec &chain_spec, + const primitives::GenesisBlockHeader &genesis, + std::shared_ptr repository, + std::shared_ptr storage, + std::shared_ptr env_factory); + + outcome::result onRxRequest(RequestType req, + std::shared_ptr) override; + + void onTxRequest(const RequestType &) override {} + + private: + std::shared_ptr repository_; + std::shared_ptr storage_; + std::shared_ptr env_factory_; + }; +} // namespace kagome::network + +#endif // KAGOME_NETWORK_IMPL_PROTOCOLS_LIGHT_HPP diff --git a/core/network/impl/router_libp2p.cpp b/core/network/impl/router_libp2p.cpp index 7068bd162b..77e380b017 100644 --- a/core/network/impl/router_libp2p.cpp +++ b/core/network/impl/router_libp2p.cpp @@ -14,6 +14,7 @@ namespace kagome::network { const BootstrapNodes &bootstrap_nodes, std::shared_ptr ping_proto, boost::di::extension::lazy> warp_protocol, + std::shared_ptr light_protocol, std::shared_ptr protocol_factory) : app_state_manager_{app_state_manager}, host_{host}, @@ -22,6 +23,7 @@ namespace kagome::network { log_{log::createLogger("RouterLibp2p", "network")}, ping_protocol_{std::move(ping_proto)}, warp_protocol_{std::move(warp_protocol)}, + light_protocol_{std::move(light_protocol)}, protocol_factory_{std::move(protocol_factory)} { BOOST_ASSERT(app_state_manager_ != nullptr); BOOST_ASSERT(ping_protocol_ != nullptr); @@ -62,6 +64,8 @@ namespace kagome::network { warp_protocol_.get()->start(); + light_protocol_->start(); + block_announce_protocol_ = protocol_factory_->makeBlockAnnounceProtocol(); if (not block_announce_protocol_) { return false; diff --git a/core/network/impl/router_libp2p.hpp b/core/network/impl/router_libp2p.hpp index 5ec4e44831..c31e68799d 100644 --- a/core/network/impl/router_libp2p.hpp +++ b/core/network/impl/router_libp2p.hpp @@ -16,6 +16,7 @@ #include "libp2p/host/host.hpp" #include "libp2p/multi/multiaddress.hpp" #include "libp2p/protocol/ping.hpp" +#include "network/impl/protocols/light.hpp" #include "network/impl/protocols/protocol_factory.hpp" #include "network/sync_protocol_observer.hpp" #include "network/types/bootstrap_nodes.hpp" @@ -42,6 +43,7 @@ namespace kagome::network { const BootstrapNodes &bootstrap_nodes, std::shared_ptr ping_proto, boost::di::extension::lazy> warp_protocol, + std::shared_ptr light_protocol, std::shared_ptr protocol_factory); ~RouterLibp2p() override = default; @@ -86,6 +88,7 @@ namespace kagome::network { log::Logger log_; std::shared_ptr ping_protocol_; boost::di::extension::lazy> warp_protocol_; + std::shared_ptr light_protocol_; std::shared_ptr protocol_factory_; std::shared_ptr block_announce_protocol_; diff --git a/core/network/protobuf/CMakeLists.txt b/core/network/protobuf/CMakeLists.txt index a817b708bf..976f6ef7dc 100644 --- a/core/network/protobuf/CMakeLists.txt +++ b/core/network/protobuf/CMakeLists.txt @@ -12,3 +12,13 @@ target_include_directories(node_api_proto PUBLIC # required for compiling proto targets $ ) + +add_proto_library(light_api_proto + light.v1.proto + ) +target_include_directories(light_api_proto PUBLIC + # required for common targets + $ + # required for compiling proto targets + $ + ) diff --git a/core/network/protobuf/light.v1.proto b/core/network/protobuf/light.v1.proto new file mode 100644 index 0000000000..afe3fcf417 --- /dev/null +++ b/core/network/protobuf/light.v1.proto @@ -0,0 +1,73 @@ +// Schema definition for light client messages. + +syntax = "proto3"; + +package protobuf_generated.api.v1.light; + +// A pair of arbitrary bytes. +message Pair { + // The first element of the pair. + bytes fst = 1; + // The second element of the pair. + bytes snd = 2; +} + +// Enumerate all possible light client request messages. +message Request { + oneof request { + RemoteCallRequest remote_call_request = 1; + RemoteReadRequest remote_read_request = 2; + RemoteReadChildRequest remote_read_child_request = 4; + // Note: ids 3 and 5 were used in the past. It would be preferable to not re-use them. + } +} + +// Enumerate all possible light client response messages. +message Response { + oneof response { + RemoteCallResponse remote_call_response = 1; + RemoteReadResponse remote_read_response = 2; + // Note: ids 3 and 4 were used in the past. It would be preferable to not re-use them. + } +} + +// Remote call request. +message RemoteCallRequest { + // Block at which to perform call. + bytes block = 2; + // Method name. + string method = 3; + // Call data. + bytes data = 4; +} + +// Remote call response. +message RemoteCallResponse { + // Execution proof. + bytes proof = 2; +} + +// Remote storage read request. +message RemoteReadRequest { + // Block at which to perform call. + bytes block = 2; + // Storage keys. + repeated bytes keys = 3; +} + +// Remote read response. +message RemoteReadResponse { + // Read proof. + bytes proof = 2; +} + +// Remote storage read child request. +message RemoteReadChildRequest { + // Block at which to perform call. + bytes block = 2; + // Child Storage key, this is relative + // to the child type storage location. + bytes storage_key = 3; + // Storage keys. + repeated bytes keys = 6; +} diff --git a/core/network/warp/cache.hpp b/core/network/warp/cache.hpp index 2317bb7636..14e3883132 100644 --- a/core/network/warp/cache.hpp +++ b/core/network/warp/cache.hpp @@ -49,8 +49,8 @@ namespace kagome::network { mutable storage::MapPrefix db_prefix_; log::Logger log_; std::shared_ptr chain_sub_; - std::atomic_bool started_; - std::atomic_bool caching_; + std::atomic_bool started_ = false; + std::atomic_bool caching_ = false; primitives::BlockNumber cache_next_ = 0; }; } // namespace kagome::network