Kagome
Polkadot Runtime Engine in C++17
state_api_impl.cpp
Go to the documentation of this file.
1 
7 
8 #include <boost/algorithm/string/predicate.hpp>
9 #include <unordered_map>
10 #include <utility>
11 
12 #include <jsonrpc-lean/fault.h>
13 
14 #include "common/hexutil.hpp"
15 #include "common/monadic_utils.hpp"
17 
20  switch (e) {
21  case E::MAX_BLOCK_RANGE_EXCEEDED:
22  return "Maximum block range size ("
24  + " blocks) exceeded";
25  case E::MAX_KEY_SET_SIZE_EXCEEDED:
26  return "Maximum key set size ("
28  + " keys) exceeded";
29  case E::END_BLOCK_LOWER_THAN_BEGIN_BLOCK:
30  return "End block is lower (is an ancestor of) the begin block "
31  "(should be the other way)";
32  }
33  return "Unknown State API error";
34 }
35 
36 namespace kagome::api {
37 
39  std::shared_ptr<blockchain::BlockHeaderRepository> block_repo,
40  std::shared_ptr<const storage::trie::TrieStorage> trie_storage,
41  std::shared_ptr<blockchain::BlockTree> block_tree,
42  std::shared_ptr<runtime::Core> runtime_core,
43  std::shared_ptr<runtime::Metadata> metadata,
44  std::shared_ptr<runtime::RawExecutor> executor)
45  : header_repo_{std::move(block_repo)},
46  storage_{std::move(trie_storage)},
47  block_tree_{std::move(block_tree)},
48  runtime_core_{std::move(runtime_core)},
49  metadata_{std::move(metadata)},
50  executor_{std::move(executor)} {
51  BOOST_ASSERT(nullptr != header_repo_);
52  BOOST_ASSERT(nullptr != storage_);
53  BOOST_ASSERT(nullptr != block_tree_);
54  BOOST_ASSERT(nullptr != runtime_core_);
55  BOOST_ASSERT(nullptr != metadata_);
56  BOOST_ASSERT(nullptr != executor_);
57  }
58 
60  std::shared_ptr<api::ApiService> const &api_service) {
61  BOOST_ASSERT(api_service != nullptr);
62  api_service_ = api_service;
63  }
64 
65  outcome::result<common::Buffer> StateApiImpl::call(
66  std::string_view method,
67  common::Buffer data,
68  const std::optional<primitives::BlockHash> &opt_at) const {
69  auto at =
70  opt_at.has_value() ? opt_at.value() : block_tree_->deepestLeaf().hash;
71  return executor_->callAtRaw(at, method, data);
72  }
73 
74  outcome::result<std::vector<common::Buffer>> StateApiImpl::getKeysPaged(
75  const std::optional<common::BufferView> &prefix_opt,
76  uint32_t keys_amount,
77  const std::optional<common::BufferView> &prev_key_opt,
78  const std::optional<primitives::BlockHash> &block_hash_opt) const {
79  const auto &prefix = prefix_opt.value_or(common::kEmptyBuffer);
80  const auto &prev_key = prev_key_opt.value_or(prefix);
81  const auto &block_hash =
82  block_hash_opt.value_or(block_tree_->getLastFinalized().hash);
83 
84  OUTCOME_TRY(header, header_repo_->getBlockHeader(block_hash));
85  OUTCOME_TRY(initial_trie_reader,
86  storage_->getEphemeralBatchAt(header.state_root));
87  auto cursor = initial_trie_reader->trieCursor();
88 
89  // if prev_key is bigger than prefix, then set cursor to the next key after
90  // prev_key
91  if (prev_key > prefix) {
92  OUTCOME_TRY(cursor->seekUpperBound(prev_key));
93  }
94  // otherwise set cursor to key that is next to or equal to prefix
95  else {
96  OUTCOME_TRY(cursor->seekLowerBound(prefix));
97  }
98 
99  std::vector<common::Buffer> result{};
100  result.reserve(keys_amount);
101  for (uint32_t i = 0; i < keys_amount && cursor->isValid(); ++i) {
102  auto key = cursor->key();
103  BOOST_ASSERT(key.has_value());
104 
105  // make sure our key begins with prefix
106  if (!boost::starts_with(key.value(), prefix)) {
107  break;
108  }
109  result.push_back(cursor->key().value());
110  OUTCOME_TRY(cursor->next());
111  }
112 
113  return result;
114  }
115 
116  outcome::result<std::optional<common::Buffer>> StateApiImpl::getStorage(
117  const common::BufferView &key) const {
118  auto last_finalized = block_tree_->getLastFinalized();
119  return getStorageAt(key, last_finalized.hash);
120  }
121 
122  outcome::result<std::optional<common::Buffer>> StateApiImpl::getStorageAt(
123  const common::BufferView &key, const primitives::BlockHash &at) const {
124  OUTCOME_TRY(header, header_repo_->getBlockHeader(at));
125  OUTCOME_TRY(trie_reader, storage_->getEphemeralBatchAt(header.state_root));
126  auto res = trie_reader->tryGet(key);
127  return common::map_result_optional(res,
128  [](const auto &r) { return r.get(); });
129  }
130 
131  outcome::result<std::vector<StateApiImpl::StorageChangeSet>>
133  gsl::span<const common::Buffer> keys,
134  const primitives::BlockHash &from,
135  std::optional<primitives::BlockHash> opt_to) const {
136  // TODO(Harrm): Optimize once changes trie is enabled (and a warning/assert
137  // for now that will fire once it is, just not to forget)
138  auto to =
139  opt_to.has_value() ? opt_to.value() : block_tree_->deepestLeaf().hash;
140  if (keys.size() > static_cast<ssize_t>(kMaxKeySetSize)) {
142  }
143 
144  if (from != to) {
145  OUTCOME_TRY(from_number, header_repo_->getNumberByHash(from));
146  OUTCOME_TRY(to_number, header_repo_->getNumberByHash(to));
147  if (to_number < from_number) {
149  }
150  if (to_number - from_number > kMaxBlockRange) {
152  }
153  }
154 
155  std::vector<StorageChangeSet> changes;
156  std::map<gsl::span<const uint8_t>, std::optional<common::Buffer>>
157  last_values;
158 
159  // TODO(Harrm): optimize it to use a lazy generator instead of returning the
160  // whole vector with block ids
161  OUTCOME_TRY(range, block_tree_->getChainByBlocks(from, to));
162  for (auto &block : range) {
163  OUTCOME_TRY(header, header_repo_->getBlockHeader(block));
164  OUTCOME_TRY(batch, storage_->getEphemeralBatchAt(header.state_root));
165  StorageChangeSet change{block, {}};
166  for (auto &key : keys) {
167  OUTCOME_TRY(opt_value, batch->tryGet(key));
168  auto it = last_values.find(key);
169  if (it == last_values.end() || it->second != opt_value) {
170  std::optional<common::Buffer> opt_buffer =
171  opt_value ? std::make_optional(opt_value.value().get())
172  : std::nullopt;
173  change.changes.push_back(
174  StorageChangeSet::Change{common::Buffer{key}, opt_buffer});
175  }
176  last_values[key] = std::move(opt_value);
177  }
178  if (!change.changes.empty()) {
179  changes.emplace_back(std::move(change));
180  }
181  }
182  return changes;
183  }
184 
185  outcome::result<std::vector<StateApiImpl::StorageChangeSet>>
187  gsl::span<const common::Buffer> keys,
188  std::optional<primitives::BlockHash> opt_at) const {
189  auto at =
190  opt_at.has_value() ? opt_at.value() : block_tree_->deepestLeaf().hash;
191  return queryStorage(keys, at, at);
192  }
193 
194  outcome::result<primitives::Version> StateApiImpl::getRuntimeVersion(
195  const std::optional<primitives::BlockHash> &at) const {
196  if (at) {
197  return runtime_core_->version(at.value());
198  }
199  return runtime_core_->version(block_tree_->deepestLeaf().hash);
200  }
201 
202  outcome::result<uint32_t> StateApiImpl::subscribeStorage(
203  const std::vector<common::Buffer> &keys) {
204  if (auto api_service = api_service_.lock()) {
205  return api_service->subscribeSessionToKeys(keys);
206  }
207 
208  throw jsonrpc::InternalErrorFault(
209  "Internal error. Api service not initialized.");
210  }
211 
212  outcome::result<bool> StateApiImpl::unsubscribeStorage(
213  const std::vector<uint32_t> &subscription_id) {
214  if (auto api_service = api_service_.lock())
215  return api_service->unsubscribeSessionFromIds(subscription_id);
216 
217  throw jsonrpc::InternalErrorFault(
218  "Internal error. Api service not initialized.");
219  }
220 
221  outcome::result<uint32_t> StateApiImpl::subscribeRuntimeVersion() {
222  if (auto api_service = api_service_.lock()) {
223  return api_service->subscribeRuntimeVersion();
224  }
225 
226  throw jsonrpc::InternalErrorFault(
227  "Internal error. Api service not initialized.");
228  }
229 
231  uint32_t subscription_id) {
232  if (auto api_service = api_service_.lock()) {
233  OUTCOME_TRY(api_service->unsubscribeRuntimeVersion(subscription_id));
234  return outcome::success();
235  }
236 
237  throw jsonrpc::InternalErrorFault(
238  "Internal error. Api service not initialized.");
239  }
240 
241  outcome::result<std::string> StateApiImpl::getMetadata() {
242  OUTCOME_TRY(data, metadata_->metadata(block_tree_->deepestLeaf().hash));
243  return common::hex_lower_0x(data);
244  }
245 
246  outcome::result<std::string> StateApiImpl::getMetadata(
247  std::string_view hex_block_hash) {
248  OUTCOME_TRY(h, primitives::BlockHash::fromHexWithPrefix(hex_block_hash));
249  OUTCOME_TRY(data, metadata_->metadata(h));
250  return common::hex_lower_0x(data);
251  }
252 } // namespace kagome::api
outcome::result< common::Buffer > call(std::string_view method, common::Buffer data, const std::optional< primitives::BlockHash > &opt_at) const override
Class represents arbitrary (including empty) byte buffer.
Definition: buffer.hpp:29
outcome::result< primitives::Version > getRuntimeVersion(const std::optional< primitives::BlockHash > &at) const override
OUTCOME_CPP_DEFINE_CATEGORY(kagome::api, StateApiImpl::Error, e)
StateApiImpl(std::shared_ptr< blockchain::BlockHeaderRepository > block_repo, std::shared_ptr< const storage::trie::TrieStorage > trie_storage, std::shared_ptr< blockchain::BlockTree > block_tree, std::shared_ptr< runtime::Core > runtime_core, std::shared_ptr< runtime::Metadata > metadata, std::shared_ptr< runtime::RawExecutor > executor)
std::string_view to_string(SlotType s)
Definition: slot.hpp:22
static constexpr size_t kMaxBlockRange
std::shared_ptr< runtime::Core > runtime_core_
static constexpr size_t kMaxKeySetSize
outcome::result< std::vector< common::Buffer > > getKeysPaged(const std::optional< common::BufferView > &prefix, uint32_t keys_amount, const std::optional< common::BufferView > &prev_key, const std::optional< primitives::BlockHash > &block_hash_opt) const override
outcome::result< uint32_t > subscribeRuntimeVersion() override
outcome::result< std::optional< common::Buffer > > getStorage(const common::BufferView &key) const override
outcome::result< void > unsubscribeRuntimeVersion(uint32_t subscription_id) override
static outcome::result< Blob< size_ > > fromHexWithPrefix(std::string_view hex)
Definition: blob.hpp:197
outcome::result< std::vector< StorageChangeSet > > queryStorage(gsl::span< const common::Buffer > keys, const primitives::BlockHash &from, std::optional< primitives::BlockHash > to) const override
outcome::result< std::string > getMetadata() override
std::shared_ptr< blockchain::BlockHeaderRepository > header_repo_
outcome::result< uint32_t > subscribeStorage(const std::vector< common::Buffer > &keys) override
std::string hex_lower_0x(gsl::span< const uint8_t > bytes) noexcept
Converts bytes to hex representation with prefix 0x.
Definition: hexutil.cpp:58
std::shared_ptr< blockchain::BlockTree > block_tree_
std::shared_ptr< runtime::Metadata > metadata_
outcome::result< bool > unsubscribeStorage(const std::vector< uint32_t > &subscription_id) override
static const Buffer kEmptyBuffer
Definition: buffer.hpp:246
std::shared_ptr< runtime::RawExecutor > executor_
std::weak_ptr< api::ApiService > api_service_
std::shared_ptr< const storage::trie::TrieStorage > storage_
outcome::result< std::vector< StorageChangeSet > > queryStorageAt(gsl::span< const common::Buffer > keys, std::optional< primitives::BlockHash > at) const override
void setApiService(std::shared_ptr< api::ApiService > const &api_service) override
outcome::result< std::optional< common::Buffer > > getStorageAt(const common::BufferView &key, const primitives::BlockHash &at) const override
outcome::result< std::optional< R > > map_result_optional(outcome::result< std::optional< T >> const &res_opt, F const &f)