Kagome
Polkadot Runtime Engine in C++17
storage_extension.cpp
Go to the documentation of this file.
1 
7 
8 #include <algorithm>
9 
11 #include "log/profiling_logger.hpp"
14 #include "runtime/ptr_size.hpp"
16 #include "scale/encode_append.hpp"
21 
23 
24 namespace {
25  [[nodiscard]] kagome::storage::trie::StateVersion toStateVersion(
26  kagome::runtime::WasmI32 state_version_int) {
27  if (state_version_int == 0) {
29  } else if (state_version_int == 1) {
30  // TODO(xDimon): remove exception when new version will be implemented
31  throw std::runtime_error("StateVersion::V1 is not implemented");
33  } else {
34  throw std::runtime_error(fmt::format(
35  "Invalid state version: {}. Expected 0 or 1", state_version_int));
36  }
37  }
38 } // namespace
39 
40 namespace kagome::host_api {
42  std::shared_ptr<runtime::TrieStorageProvider> storage_provider,
43  std::shared_ptr<const runtime::MemoryProvider> memory_provider)
44  : storage_provider_(std::move(storage_provider)),
45  memory_provider_(std::move(memory_provider)),
46  logger_{log::createLogger("StorageExtension", "storage_extension")} {
47  BOOST_ASSERT_MSG(storage_provider_ != nullptr, "storage batch is nullptr");
48  BOOST_ASSERT_MSG(memory_provider_ != nullptr, "memory provider is nullptr");
49  }
50 
52  // rollback will have value until there are opened transactions that need
53  // to be closed
54  while (true) {
55  if (auto res = storage_provider_->rollbackTransaction();
56  res.has_error()) {
57  if (res.error()
59  logger_->error(res.error().message());
60  }
61  break;
62  }
63  }
64  }
65 
66  // -------------------------Data storage--------------------------
67 
69  runtime::WasmSpan key_pos,
70  runtime::WasmSpan value_out,
71  runtime::WasmOffset offset) {
72  auto [key_ptr, key_size] = runtime::PtrSize(key_pos);
73  auto value = runtime::PtrSize(value_out);
74  auto &memory = memory_provider_->getCurrentMemory()->get();
75 
76  auto key = memory.loadN(key_ptr, key_size);
77  std::optional<uint32_t> res{std::nullopt};
78  if (auto data_opt_res = get(key); data_opt_res.has_value()) {
79  auto &data_opt = data_opt_res.value();
80  if (data_opt.has_value()) {
81  common::BufferView data = data_opt.value().get();
82  data = data.subspan(std::min<size_t>(offset, data.size()));
83  auto written = std::min<size_t>(data.size(), value.size);
84  memory.storeBuffer(value.ptr, data.subspan(0, written));
85  res = data.size();
86 
88  logger_, data, key, common::Buffer{data.subspan(0, written)});
89  } else {
91  logger_, std::string_view{"none"}, key, value_out, offset);
92  }
93  } else {
94  SL_ERROR(logger_,
95  "Error in ext_storage_read_version_1: {}",
96  data_opt_res.error().message());
97  }
98  return memory.storeBuffer(scale::encode(res).value());
99  }
100 
101  outcome::result<std::optional<common::BufferConstRef>> StorageExtension::get(
102  const common::BufferView &key) const {
103  auto batch = storage_provider_->getCurrentBatch();
104  return batch->tryGet(key);
105  }
106 
108  auto [key_ptr, key_size] = runtime::PtrSize(key);
109  auto &memory = memory_provider_->getCurrentMemory()->get();
110  return memory.loadN(key_ptr, key_size);
111  }
112 
113  outcome::result<std::optional<Buffer>> StorageExtension::getStorageNextKey(
114  const common::Buffer &key) const {
115  auto batch = storage_provider_->getCurrentBatch();
116  auto cursor = batch->trieCursor();
117  OUTCOME_TRY(cursor->seekUpperBound(key));
118  return cursor->key();
119  }
120 
122  runtime::WasmSpan key_span, runtime::WasmSpan value_span) {
123  auto [key_ptr, key_size] = runtime::PtrSize(key_span);
124  auto [value_ptr, value_size] = runtime::PtrSize(value_span);
125  auto &memory = memory_provider_->getCurrentMemory()->get();
126  auto key = memory.loadN(key_ptr, key_size);
127  auto value = memory.loadN(value_ptr, value_size);
128 
129  SL_TRACE_VOID_FUNC_CALL(logger_, key, value);
130 
131  auto batch = storage_provider_->getCurrentBatch();
132  auto put_result = batch->put(key, value);
133  if (not put_result) {
134  logger_->error(
135  "ext_set_storage failed, due to fail in trie db with reason: {}",
136  put_result.error().message());
137  }
138  }
139 
141  runtime::WasmSpan key) {
142  auto [key_ptr, key_size] = runtime::PtrSize(key);
143  auto &memory = memory_provider_->getCurrentMemory()->get();
144  auto key_buffer = memory.loadN(key_ptr, key_size);
145 
146  constexpr auto error_message =
147  "ext_storage_get_version_1( {} ) => value was not obtained. Reason: {}";
148 
149  auto result = get(key_buffer);
150 
151  if (result) {
152  SL_TRACE_FUNC_CALL(logger_, result.value(), key_buffer);
153  } else {
154  logger_->error(
155  error_message, key_buffer.toHex(), result.error().message());
156  }
157 
158  auto &option = result.value();
159 
160  return memory.storeBuffer(scale::encode(option).value());
161  }
162 
164  runtime::WasmSpan key_data) {
165  auto [key_ptr, key_size] = runtime::PtrSize(key_data);
166  auto batch = storage_provider_->getCurrentBatch();
167  auto &memory = memory_provider_->getCurrentMemory()->get();
168  auto key = memory.loadN(key_ptr, key_size);
169  auto del_result = batch->remove(key);
170  SL_TRACE_FUNC_CALL(logger_, del_result.has_value(), key);
171  if (not del_result) {
172  logger_->warn(
173  "ext_storage_clear_version_1 did not delete key {} from trie db "
174  "with reason: {}",
175  key_data,
176  del_result.error().message());
177  }
178  }
179 
181  runtime::WasmSpan key_data) const {
182  auto [key_ptr, key_size] = runtime::PtrSize(key_data);
183  auto batch = storage_provider_->getCurrentBatch();
184  auto &memory = memory_provider_->getCurrentMemory()->get();
185  auto key = memory.loadN(key_ptr, key_size);
186  auto res = batch->contains(key);
187  return (res.has_value() and res.value()) ? 1 : 0;
188  }
189 
191  runtime::WasmSpan prefix_span) {
192  auto [prefix_ptr, prefix_size] = runtime::PtrSize(prefix_span);
193  auto &memory = memory_provider_->getCurrentMemory()->get();
194  auto prefix = memory.loadN(prefix_ptr, prefix_size);
196  (void)clearPrefix(prefix, std::nullopt);
197  }
198 
200  runtime::WasmSpan prefix_span, runtime::WasmSpan limit_span) {
201  auto [prefix_ptr, prefix_size] = runtime::PtrSize(prefix_span);
202  auto [limit_ptr, limit_size] = runtime::PtrSize(limit_span);
203  auto &memory = memory_provider_->getCurrentMemory()->get();
204  auto prefix = memory.loadN(prefix_ptr, prefix_size);
205  auto enc_limit = memory.loadN(limit_ptr, limit_size);
206  auto limit_res = scale::decode<std::optional<uint32_t>>(enc_limit);
207  if (!limit_res) {
208  auto msg = fmt::format(
209  "ext_storage_clear_prefix_version_2 failed at decoding second "
210  "argument: {}",
211  limit_res.error());
212  logger_->error(msg);
213  throw std::runtime_error(msg);
214  }
215  auto limit_opt = std::move(limit_res.value());
216  if (limit_opt) {
217  SL_TRACE_VOID_FUNC_CALL(logger_, prefix, limit_opt.value());
218  } else {
219  SL_TRACE_VOID_FUNC_CALL(logger_, prefix, std::string_view{"none"});
220  }
221  return clearPrefix(prefix, limit_opt);
222  }
223 
226  }
227 
230  [[maybe_unused]] auto state_version = toStateVersion(version);
231 
232  outcome::result<storage::trie::RootHash> res{{}};
234  if (auto opt_batch = storage_provider_->tryGetPersistentBatch();
235  opt_batch.has_value() and opt_batch.value() != nullptr) {
236  res = opt_batch.value()->commit();
237  } else {
238  logger_->warn("ext_storage_root called in an ephemeral extension");
239  res = storage_provider_->forceCommit();
240  }
241  if (res.has_error()) {
242  logger_->error("ext_storage_root resulted with an error: {}",
243  res.error().message());
244  }
245  const auto &root = res.value();
246  auto &memory = memory_provider_->getCurrentMemory()->get();
247  return memory.storeBuffer(root);
248  }
249 
251  runtime::WasmSpan parent_hash_data) {
252  auto &memory = memory_provider_->getCurrentMemory()->get();
253  // https://github.com/paritytech/substrate/pull/10080
254  return memory.storeBuffer(scale::encode(std::optional<Buffer>()).value());
255  }
256 
258  runtime::WasmSpan key_span) const {
259  static constexpr runtime::WasmSpan kErrorSpan = -1;
260 
261  auto [key_ptr, key_size] = runtime::PtrSize(key_span);
262  auto &memory = memory_provider_->getCurrentMemory()->get();
263  auto key_bytes = memory.loadN(key_ptr, key_size);
264  auto res = getStorageNextKey(key_bytes);
265  if (res.has_error()) {
266  logger_->error("ext_storage_next_key resulted with error: {}",
267  res.error().message());
268  return kErrorSpan;
269  }
270  auto &&next_key_opt = res.value();
271  if (auto enc_res = scale::encode(next_key_opt); enc_res.has_value()) {
273  res.value().has_value()
274  ? res.value().value()
275  : common::Buffer().put("no value"),
276  key_bytes);
277  return memory.storeBuffer(enc_res.value());
278  } else { // NOLINT(readability-else-after-return)
279  logger_->error(
280  "ext_storage_next_key result encoding resulted with error: {}",
281  enc_res.error().message());
282  }
283  return kErrorSpan;
284  }
285 
287  runtime::WasmSpan key_span, runtime::WasmSpan append_span) const {
288  auto [key_ptr, key_size] = runtime::PtrSize(key_span);
289  auto [append_ptr, append_size] = runtime::PtrSize(append_span);
290  auto &memory = memory_provider_->getCurrentMemory()->get();
291  auto key_bytes = memory.loadN(key_ptr, key_size);
292  auto append_bytes = memory.loadN(append_ptr, append_size);
293 
294  auto val_opt_res = get(key_bytes);
295  if (val_opt_res.has_error()) {
296  throw std::runtime_error{
297  fmt::format("Error fetching value from storage: {}",
298  val_opt_res.error().message())};
299  }
300  auto &val_opt = val_opt_res.value();
301  auto &&val = val_opt ? common::Buffer{val_opt.value()} : common::Buffer{};
302 
303  if (scale::append_or_new_vec(val.asVector(), append_bytes).has_value()) {
304  auto batch = storage_provider_->getCurrentBatch();
305  SL_TRACE_VOID_FUNC_CALL(logger_, key_bytes, val);
306  auto put_result = batch->put(key_bytes, std::move(val));
307  if (not put_result) {
308  logger_->error(
309  "ext_storage_append_version_1 failed, due to fail in trie db "
310  "with reason: {}",
311  put_result.error().message());
312  }
313  return;
314  }
315  }
316 
318  auto res = storage_provider_->startTransaction();
319  if (res.has_error()) {
320  logger_->error("Storage transaction start has failed: {}",
321  res.error().message());
322  throw std::runtime_error(res.error().message());
323  }
324  }
325 
327  auto res = storage_provider_->commitTransaction();
329  if (res.has_error()) {
330  logger_->error("Storage transaction rollback has failed: {}",
331  res.error().message());
332  throw std::runtime_error(res.error().message());
333  }
334  }
335 
337  auto res = storage_provider_->rollbackTransaction();
339  if (res.has_error()) {
340  logger_->error("Storage transaction commit has failed: {}",
341  res.error().message());
342  throw std::runtime_error(res.error().message());
343  }
344  }
345 
346  namespace {
350  using KeyValueCollection =
351  std::vector<std::pair<common::Buffer, common::Buffer>>;
356  using ValuesCollection = std::vector<common::Buffer>;
357  } // namespace
358 
360  runtime::WasmSpan values_data) {
361  auto [ptr, size] = runtime::PtrSize(values_data);
362  auto &memory = memory_provider_->getCurrentMemory()->get();
363  const auto &buffer = memory.loadN(ptr, size);
364  const auto &pairs = scale::decode<KeyValueCollection>(buffer);
365  if (!pairs) {
366  logger_->error("failed to decode pairs: {}", pairs.error().message());
367  throw std::runtime_error(pairs.error().message());
368  }
369 
370  auto &&pv = pairs.value();
372  if (pv.empty()) {
373  static const auto empty_root =
375  auto res = memory.storeBuffer(empty_root);
376  return runtime::PtrSize(res).ptr;
377  }
379  for (auto &&p : pv) {
380  auto &&key = p.first;
381  auto &&value = p.second;
382  // already scale-encoded
383  auto put_res = trie.put(key, value);
384  if (not put_res) {
385  logger_->error(
386  "Insertion of value {} with key {} into the trie failed due to "
387  "error: {}",
388  value.toHex(),
389  key.toHex(),
390  put_res.error().message());
391  }
392  }
393  const auto &enc = codec.encodeNode(*trie.getRoot());
394  if (!enc) {
395  logger_->error("failed to encode trie root: {}", enc.error().message());
396  throw std::runtime_error(enc.error().message());
397  }
398  const auto &hash = codec.hash256(enc.value());
399 
400  auto res = memory.storeBuffer(hash);
401  return runtime::PtrSize(res).ptr;
402  }
403 
406  runtime::WasmSpan values_data) {
408  runtime::WasmI32(0));
409  }
410 
414  auto [address, size] = runtime::PtrSize(values_data);
415  auto &memory = memory_provider_->getCurrentMemory()->get();
416  const auto &buffer = memory.loadN(address, size);
417  const auto &values = scale::decode<ValuesCollection>(buffer);
418  if (!values) {
419  logger_->error("failed to decode values: {}", values.error().message());
420  throw std::runtime_error(values.error().message());
421  }
422  const auto &collection = values.value();
423 
424  [[maybe_unused]] auto state_version = toStateVersion(version);
425 
426  auto ordered_hash = storage::trie::calculateOrderedTrieHash(
427  collection.begin(), collection.end());
428  if (!ordered_hash.has_value()) {
429  logger_->error(
430  "ext_blake2_256_enumerated_trie_root resulted with an error: {}",
431  ordered_hash.error().message());
432  throw std::runtime_error(ordered_hash.error().message());
433  }
434  SL_TRACE_FUNC_CALL(logger_, ordered_hash.value());
435  auto res = memory.storeBuffer(ordered_hash.value());
436  return runtime::PtrSize(res).ptr;
437  }
438 
440  common::BufferView prefix, std::optional<uint32_t> limit) {
441  auto batch = storage_provider_->getCurrentBatch();
442  auto &memory = memory_provider_->getCurrentMemory()->get();
443 
444  auto res = batch->clearPrefix(
445  prefix, limit ? std::optional<uint64_t>(limit.value()) : std::nullopt);
446  if (not res) {
447  auto msg = fmt::format("ext_storage_clear_prefix failed: {}",
448  res.error().message());
449  logger_->error(msg);
450  throw std::runtime_error(msg);
451  }
452  auto enc_res = scale::encode(res.value());
453  if (not enc_res) {
454  auto msg = fmt::format("ext_storage_clear_prefix failed: {}",
455  enc_res.error().message());
456  logger_->error(msg);
457  throw std::runtime_error(msg);
458  }
459  return memory.storeBuffer(enc_res.value());
460  }
461 
463  static const auto &prefix = storage::kChildStorageDefaultPrefix;
464  static const auto empty_hash = codec_.hash256(common::Buffer{0});
465  auto current_key = prefix;
466  auto key_res = getStorageNextKey(current_key);
467  while (key_res.has_value() and key_res.value().has_value()) {
468  auto &key_opt = key_res.value();
469  current_key = key_opt.value();
470 
471  bool contains_prefix =
472  std::equal(prefix.begin(), prefix.end(), current_key.begin());
473  if (not contains_prefix) {
474  break;
475  }
476  // SAFETY: key obtained by getStorageNextKey method, thus must exist in
477  // the storage
478  auto value_opt = get(current_key).value();
479  if (value_opt and value_opt.value().get() == empty_hash) {
480  auto batch = storage_provider_->getCurrentBatch();
481  auto remove_res = batch->remove(current_key);
482  if (not remove_res) {
483  logger_->error(
484  "Unable to remove empty child storage under key {}, error is {}",
485  current_key,
486  remove_res.error().message());
487  } else {
488  SL_TRACE(
489  logger_, "Removed empty child trie under key {}", current_key);
490  }
491  }
492  key_res = getStorageNextKey(current_key);
493  }
494  }
495 
496 } // namespace kagome::host_api
runtime::WasmSpan ext_storage_read_version_1(runtime::WasmSpan key, runtime::WasmSpan value_out, runtime::WasmOffset offset)
Class represents arbitrary (including empty) byte buffer.
Definition: buffer.hpp:29
uint32_t WasmSize
Size type is uint32_t because we are working in 32 bit address space.
Definition: types.hpp:35
void ext_storage_append_version_1(runtime::WasmSpan key, runtime::WasmSpan value) const
WasmPointer ptr
address of buffer
Definition: ptr_size.hpp:40
runtime::WasmPointer ext_trie_blake2_256_ordered_root_version_1(runtime::WasmSpan values_data)
#define SL_TRACE_FUNC_CALL(logger, ret,...)
Definition: logger.hpp:142
STL namespace.
std::shared_ptr< runtime::TrieStorageProvider > storage_provider_
outcome::result< std::optional< common::Buffer > > getStorageNextKey(const common::Buffer &key) const
void ext_storage_clear_prefix_version_1(runtime::WasmSpan prefix)
uint32_t WasmOffset
Offset type is uint32_t because we are working in 32 bit address space.
Definition: types.hpp:44
int32_t WasmI32
Definition: types.hpp:46
runtime::WasmPointer ext_trie_blake2_256_root_version_1(runtime::WasmSpan values_data)
runtime::WasmSpan ext_storage_root_version_1()
runtime::WasmSpan clearPrefix(common::BufferView prefix, std::optional< uint32_t > limit)
storage::trie::PolkadotCodec codec_
runtime::WasmSpan ext_storage_changes_root_version_1(runtime::WasmSpan parent_hash)
runtime::WasmSize ext_storage_exists_version_1(runtime::WasmSpan key_data) const
void ext_storage_clear_version_1(runtime::WasmSpan key_data)
const common::Buffer kChildStorageDefaultPrefix
string version
Definition: conf.py:16
SLBuffer< std::numeric_limits< size_t >::max()> Buffer
Definition: buffer.hpp:244
std::shared_ptr< const runtime::MemoryProvider > memory_provider_
runtime::WasmSpan ext_storage_get_version_1(runtime::WasmSpan key)
runtime::WasmPointer ext_trie_blake2_256_ordered_root_version_2(runtime::WasmSpan values_data, runtime::WasmI32 state_version)
uint64_t WasmSpan
combination of pointer and size, where less significant part represents wasm pointer, and most significant represents size
Definition: types.hpp:31
outcome::result< common::Buffer > encodeNode(const Node &node) const
Encode node to byte representation.
Definition: codec.hpp:39
outcome::result< std::optional< common::BufferConstRef > > get(const common::BufferView &key) const
common::Hash256 hash256(const BufferView &buf) const override
Get the hash of a node.
runtime::WasmSpan ext_storage_clear_prefix_version_2(runtime::WasmSpan prefix, runtime::WasmSpan limit)
StorageExtension(std::shared_ptr< runtime::TrieStorageProvider > storage_provider, std::shared_ptr< const runtime::MemoryProvider > memory_provider)
uint32_t WasmPointer
type of wasm memory is 32 bit integer
Definition: types.hpp:26
void ext_storage_set_version_1(runtime::WasmSpan key, runtime::WasmSpan value)
runtime::WasmSpan ext_storage_root_version_2(runtime::WasmI32 state_version)
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
#define SL_TRACE_VOID_FUNC_CALL(logger,...)
Definition: logger.hpp:146
outcome::result< common::Buffer > calculateOrderedTrieHash(const It &begin, const It &end)
common::Buffer loadKey(runtime::WasmSpan key) const
outcome::result< void > put(const common::BufferView &key, const common::Buffer &value) override
Store value by key.
runtime::WasmSpan ext_storage_next_key_version_1(runtime::WasmSpan key) const