Kagome
Polkadot Runtime Engine in C++17
polkadot_trie_impl.cpp
Go to the documentation of this file.
1 
7 
8 #include <functional>
9 #include <utility>
10 
13 
15 
18  switch (e) {
19  case E::INVALID_NODE_TYPE:
20  return "The trie node type is invalid";
21  }
22  return "Unknown error";
23 }
24 
25 namespace kagome::storage::trie {
26 
27  class OpaqueNodeStorage final {
28  public:
30  std::shared_ptr<TrieNode> root) noexcept
31  : retrieve_node_{std::move(node_retriever)}, root_{std::move(root)} {}
32 
33  static outcome::result<std::unique_ptr<OpaqueNodeStorage>> createAt(
34  std::shared_ptr<OpaqueTrieNode> root,
35  PolkadotTrie::NodeRetrieveFunctor node_retriever) {
36  OUTCOME_TRY(root_node, node_retriever(root));
37  return std::unique_ptr<OpaqueNodeStorage>{
38  new OpaqueNodeStorage{node_retriever, root_node}};
39  }
40 
41  [[nodiscard]] const std::shared_ptr<TrieNode> &getRoot() {
42  return root_;
43  }
44 
45  [[nodiscard]] std::shared_ptr<const TrieNode> getRoot() const {
46  return root_;
47  }
48 
49  void setRoot(const std::shared_ptr<TrieNode> &root) {
50  root_ = root;
51  }
52 
53  [[nodiscard]] outcome::result<std::shared_ptr<const TrieNode>> getChild(
54  BranchNode const &parent, uint8_t idx) const {
55  // SAFETY: changing a parent's opaque child node from a handle to a node
56  // to the actual node doesn't break it's const correctness, because opaque
57  // nodes are meant to hide their content
58  auto &mut_parent = const_cast<BranchNode &>(parent);
59  auto &opaque_child = parent.children.at(idx);
60  OUTCOME_TRY(child, retrieve_node_(opaque_child));
61  mut_parent.children.at(idx) = child;
62  return std::move(child);
63  }
64 
65  [[nodiscard]] outcome::result<std::shared_ptr<TrieNode>> getChild(
66  BranchNode const &parent, uint8_t idx) {
67  // SAFETY: adding constness, not removing
68  auto const_this = const_cast<const OpaqueNodeStorage *>(this);
69  OUTCOME_TRY(const_child, const_this->getChild(parent, idx));
70  // SAFETY: removing constness we manually added
71  auto child = std::const_pointer_cast<TrieNode>(const_child);
72  return child;
73  }
74 
75  private:
77  std::shared_ptr<TrieNode> root_;
78  };
79 } // namespace kagome::storage::trie
80 
81 namespace {
82  using namespace kagome::storage::trie;
83 
84  uint32_t getCommonPrefixLength(const NibblesView &first,
85  const NibblesView &second) {
86  auto &&[it, _] =
87  std::mismatch(first.begin(), first.end(), second.begin(), second.end());
88  return it - first.begin();
89  }
90 
99  [[nodiscard]] outcome::result<void> handleDeletion(
101  PolkadotTrie::NodePtr &parent,
102  OpaqueNodeStorage &node_storage) {
103  if (!parent->isBranch()) return outcome::success();
104  auto &branch = dynamic_cast<BranchNode &>(*parent);
105  auto bitmap = branch.childrenBitmap();
106  if (bitmap == 0) {
107  if (parent->value) {
108  // turn branch node left with no children to a leaf node
109  parent = std::make_shared<LeafNode>(parent->key_nibbles, parent->value);
110  SL_TRACE(logger, "handleDeletion: turn childless branch into a leaf");
111  } else {
112  // this case actual only for clearPrefix, unreal situation in deletion
113  parent = nullptr;
114  SL_TRACE(logger, "handleDeletion: nullify valueless branch parent");
115  }
116  } else if (branch.childrenNum() == 1 && !branch.value) {
117  size_t idx = 0;
118  while (bitmap >>= 1u) ++idx;
119  OUTCOME_TRY(child, node_storage.getChild(branch, idx));
120 
121  using T = TrieNode::Type;
122  if (child->getTrieType() == T::Leaf) {
123  parent = std::make_shared<LeafNode>(parent->key_nibbles, child->value);
124  SL_TRACE(logger,
125  "handleDeletion: turn a branch with single leaf child into "
126  "its child");
127  } else if (child->isBranch()) {
128  branch.children = dynamic_cast<BranchNode &>(*child).children;
129  parent->value = child->value;
130  SL_TRACE(logger,
131  "handleDeletion: turn a branch with single branch child into "
132  "its child");
133  }
134  parent->key_nibbles.putUint8(idx).put(child->key_nibbles);
135  }
136  return outcome::success();
137  }
138 
142  [[nodiscard]] outcome::result<void> deleteNode(
143  const kagome::log::Logger &logger,
144  PolkadotTrie::NodePtr &node,
145  const NibblesView &sought_key,
146  OpaqueNodeStorage &node_storage) {
147  if (node == nullptr) {
148  return outcome::success();
149  }
150  SL_TRACE(logger,
151  "deleteNode: currently in {}, sought key is {}",
152  node->key_nibbles.toHex(),
153  sought_key);
154 
155  if (node->isBranch()) {
156  auto &branch = dynamic_cast<BranchNode &>(*node);
157  if (node->key_nibbles == sought_key) {
158  SL_TRACE(logger, "deleteNode: deleting value in branch; stop");
159  node->value = std::nullopt;
160  } else {
161  auto length = getCommonPrefixLength(node->key_nibbles, sought_key);
162  OUTCOME_TRY(child, node_storage.getChild(branch, sought_key[length]));
163  SL_TRACE(
164  logger, "deleteNode: go to child {:x}", (int)sought_key[length]);
165  OUTCOME_TRY(deleteNode(
166  logger, child, sought_key.subspan(length + 1), node_storage));
167  branch.children[sought_key[length]] = child;
168  }
169  OUTCOME_TRY(handleDeletion(logger, node, node_storage));
170  } else if (node->key_nibbles == sought_key) {
171  SL_TRACE(logger, "deleteNode: nullifying leaf node; stop");
172  node = nullptr;
173  }
174  return outcome::success();
175  }
176 
177  outcome::result<void> notifyOnDetached(
178  PolkadotTrie::NodePtr &node,
179  const PolkadotTrie::OnDetachCallback &callback) {
180  auto key = node->key_nibbles.toByteBuffer();
181  OUTCOME_TRY(callback(key, std::move(node->value)));
182  return outcome::success();
183  }
184 
191  [[nodiscard]] outcome::result<void> detachNode(
192  const kagome::log::Logger &logger,
193  std::shared_ptr<TrieNode> &parent,
194  const NibblesView &prefix,
195  std::optional<uint64_t> limit,
196  bool &finished,
197  uint32_t &count,
198  const PolkadotTrie::OnDetachCallback &callback,
199  OpaqueNodeStorage &node_storage) {
200  if (parent == nullptr) {
201  return outcome::success();
202  }
203 
204  // means 'limit ended'
205  if (not finished) {
206  return outcome::success();
207  }
208 
209  if (std::greater_equal<size_t>()(parent->key_nibbles.size(),
210  prefix.size())) {
211  // if this is the node to be detached -- detach it
212  if (std::equal(
213  prefix.begin(), prefix.end(), parent->key_nibbles.begin())) {
214  // remove all children one by one according to limit
215  if (parent->isBranch()) {
216  auto &branch = dynamic_cast<BranchNode &>(*parent);
217  for (uint8_t child_idx = 0; child_idx < branch.kMaxChildren;
218  child_idx++) {
219  if (branch.children[child_idx] != nullptr) {
220  OUTCOME_TRY(child_node, node_storage.getChild(branch, child_idx));
221  OUTCOME_TRY(detachNode(logger,
222  child_node,
223  KeyNibbles(),
224  limit,
225  finished,
226  count,
227  callback,
228  node_storage));
229  branch.children[child_idx] = child_node;
230  }
231  }
232  }
233  if (not limit or count < limit.value()) {
234  if (parent->value) {
235  OUTCOME_TRY(notifyOnDetached(parent, callback));
236  ++count;
237  }
238  parent = nullptr;
239  } else {
240  if (parent->value) {
241  // we saw a value after limit, so not finished
242  finished = false;
243  }
244  if (parent->isBranch()) {
245  // fix block after children removal
246  OUTCOME_TRY(handleDeletion(logger, parent, node_storage));
247  }
248  }
249  return outcome::success();
250  }
251  }
252 
253  // if parent's key is smaller, and it is not a prefix of the prefix, don't
254  // change anything
255  if (not std::equal(parent->key_nibbles.begin(),
256  parent->key_nibbles.end(),
257  prefix.begin())) {
258  return outcome::success();
259  }
260 
261  if (parent->isBranch()) {
262  const auto length = parent->key_nibbles.size();
263  auto &branch = dynamic_cast<BranchNode &>(*parent);
264  auto &child = branch.children.at(prefix[length]);
265  if (child != nullptr) {
266  OUTCOME_TRY(child_node, node_storage.getChild(branch, prefix[length]));
267  OUTCOME_TRY(detachNode(logger,
268  child_node,
269  prefix.subspan(length + 1),
270  limit,
271  finished,
272  count,
273  callback,
274  node_storage));
275  branch.children[prefix[length]] = child_node;
276  OUTCOME_TRY(handleDeletion(logger, parent, node_storage));
277  }
278  }
279  return outcome::success();
280  }
281 
282 } // namespace
283 
284 namespace kagome::storage::trie {
285 
287  : nodes_{std::make_unique<OpaqueNodeStorage>(std::move(f), nullptr)},
288  logger_{log::createLogger("PolkadotTrie", "trie")} {}
289 
291  : nodes_{std::make_unique<OpaqueNodeStorage>(std::move(f), root)},
292  logger_{log::createLogger("PolkadotTrie", "trie")} {}
293 
295 
296  outcome::result<void> PolkadotTrieImpl::put(const BufferView &key,
297  const Buffer &value) {
298  auto value_copy = value;
299  return put(key, std::move(value_copy));
300  }
301 
303  return nodes_->getRoot();
304  }
305 
307  return nodes_->getRoot();
308  }
309 
310  outcome::result<void> PolkadotTrieImpl::put(const BufferView &key,
311  Buffer &&value) {
312  auto k_enc = KeyNibbles::fromByteBuffer(key);
313 
314  NodePtr root = nodes_->getRoot();
315 
316  // insert fetches a sequence of nodes (a path) from the storage and
317  // these nodes are processed in memory, so any changes applied to them
318  // will be written back to the storage only on storeNode call
319  OUTCOME_TRY(n,
320  insert(root, k_enc, std::make_shared<LeafNode>(k_enc, value)));
321  nodes_->setRoot(n);
322 
323  return outcome::success();
324  }
325 
326  outcome::result<std::tuple<bool, uint32_t>> PolkadotTrieImpl::clearPrefix(
327  const common::BufferView &prefix,
328  std::optional<uint64_t> limit,
329  const OnDetachCallback &callback) {
330  bool finished = true;
331  uint32_t count = 0;
332  auto key_nibbles = KeyNibbles::fromByteBuffer(prefix);
333  auto root = nodes_->getRoot();
334  OUTCOME_TRY(detachNode(
335  logger_, root, key_nibbles, limit, finished, count, callback, *nodes_));
336  nodes_->setRoot(root);
337  return {finished, count};
338  }
339 
340  outcome::result<PolkadotTrie::NodePtr> PolkadotTrieImpl::insert(
341  const NodePtr &parent, const NibblesView &key_nibbles, NodePtr node) {
342  using T = TrieNode::Type;
343 
344  // just update the node key and return it as the new root
345  if (parent == nullptr) {
346  node->key_nibbles = key_nibbles;
347  return node;
348  }
349 
350  const auto node_type = parent->getTrieType();
351 
352  switch (node_type) {
353  case T::BranchEmptyValue:
354  case T::BranchWithValue: {
355  auto parent_as_branch = std::dynamic_pointer_cast<BranchNode>(parent);
356  return updateBranch(parent_as_branch, key_nibbles, node);
357  }
358 
359  case T::Leaf: {
360  // need to convert this leaf into a branch
361  auto br = std::make_shared<BranchNode>();
362  auto length = getCommonPrefixLength(key_nibbles, parent->key_nibbles);
363 
364  if (parent->key_nibbles == key_nibbles
365  && key_nibbles.size() == length) {
366  node->key_nibbles = key_nibbles;
367  return node;
368  }
369 
370  br->key_nibbles = KeyNibbles{key_nibbles.subspan(0, length)};
371  auto parentKey = parent->key_nibbles;
372 
373  // value goes at this branch
374  if (key_nibbles.size() == length) {
375  br->value = node->value;
376 
377  // if we are not replacing previous leaf, then add it as a
378  // child to the new branch
379  if (static_cast<std::ptrdiff_t>(parent->key_nibbles.size())
380  > key_nibbles.size()) {
381  parent->key_nibbles = parent->key_nibbles.subbuffer(length + 1);
382  br->children.at(parentKey[length]) = parent;
383  }
384 
385  return br;
386  }
387 
388  node->key_nibbles = KeyNibbles{key_nibbles.subspan(length + 1)};
389 
390  if (length == parent->key_nibbles.size()) {
391  // if leaf's key is covered by this branch, then make the leaf's
392  // value the value at this branch
393  br->value = parent->value;
394  br->children.at(key_nibbles[length]) = node;
395  } else {
396  // otherwise, make the leaf a child of the branch and update its
397  // partial key
398  parent->key_nibbles = parent->key_nibbles.subbuffer(length + 1);
399  br->children.at(parentKey[length]) = parent;
400  br->children.at(key_nibbles[length]) = node;
401  }
402 
403  return br;
404  }
405 
406  case T::LeafContainingHashes:
408 
409  case T::BranchContainingHashes:
411 
412  case T::Empty:
414 
415  case T::ReservedForCompactEncoding:
417 
418  default:
420  }
421  }
422 
423  outcome::result<PolkadotTrie::NodePtr> PolkadotTrieImpl::updateBranch(
424  BranchPtr parent, const NibblesView &key_nibbles, const NodePtr &node) {
425  auto length = getCommonPrefixLength(key_nibbles, parent->key_nibbles);
426 
427  if (length == parent->key_nibbles.size()) {
428  // just set the value in the parent to the node value
429  if (key_nibbles == parent->key_nibbles) {
430  parent->value = node->value;
431  return parent;
432  }
433  OUTCOME_TRY(child, retrieveChild(*parent, key_nibbles[length]));
434  if (child) {
435  OUTCOME_TRY(n, insert(child, key_nibbles.subspan(length + 1), node));
436  parent->children.at(key_nibbles[length]) = n;
437  return parent;
438  }
439  node->key_nibbles = KeyNibbles{key_nibbles.subspan(length + 1)};
440  parent->children.at(key_nibbles[length]) = node;
441  return parent;
442  }
443  auto br = std::make_shared<BranchNode>(
444  KeyNibbles{key_nibbles.subspan(0, length)});
445  auto parentIdx = parent->key_nibbles[length];
446  OUTCOME_TRY(
447  new_branch,
448  insert(nullptr, parent->key_nibbles.subspan(length + 1), parent));
449  br->children.at(parentIdx) = new_branch;
450  if (key_nibbles.size() <= length) {
451  br->value = node->value;
452  } else {
453  OUTCOME_TRY(new_child,
454  insert(nullptr, key_nibbles.subspan(length + 1), node));
455  br->children.at(key_nibbles[length]) = new_child;
456  }
457  return br;
458  }
459 
460  outcome::result<common::BufferConstRef> PolkadotTrieImpl::get(
461  const common::BufferView &key) const {
462  OUTCOME_TRY(opt_value, tryGet(key));
463  if (opt_value.has_value()) {
464  return opt_value.value();
465  }
466  return TrieError::NO_VALUE;
467  }
468 
469  outcome::result<std::optional<common::BufferConstRef>>
471  if (not nodes_->getRoot()) {
472  return std::nullopt;
473  }
474  auto nibbles = KeyNibbles::fromByteBuffer(key);
475  OUTCOME_TRY(node, getNode(nodes_->getRoot(), nibbles));
476  if (node && node->value) {
477  return node->value.value();
478  }
479  return std::nullopt;
480  }
481 
482  outcome::result<PolkadotTrie::NodePtr> PolkadotTrieImpl::getNode(
483  ConstNodePtr parent, const NibblesView &key_nibbles) {
484  // SAFETY: changing a parent's opaque child node from a handle to a node
485  // to the actual node doesn't break it's const correctness, because opaque
486  // nodes are meant to hide their content
487  auto const_this = const_cast<const PolkadotTrieImpl *>(this);
488  OUTCOME_TRY(const_node, const_this->getNode(parent, key_nibbles));
489  // SAFETY: removing constancy we manually added
490  auto node = std::const_pointer_cast<TrieNode>(const_node);
491  return node;
492  }
493 
494  outcome::result<PolkadotTrie::ConstNodePtr> PolkadotTrieImpl::getNode(
495  ConstNodePtr current, const NibblesView &nibbles) const {
496  using T = TrieNode::Type;
497  if (current == nullptr) {
498  return nullptr;
499  }
500 
501  const auto node_type = current->getTrieType();
502  switch (node_type) {
503  case T::BranchEmptyValue:
504  case T::BranchWithValue: {
505  if (current->key_nibbles == nibbles or nibbles.empty()) {
506  return current;
507  }
508  if (nibbles.size() < static_cast<long>(current->key_nibbles.size())) {
509  return nullptr;
510  }
511  auto parent_as_branch =
512  std::dynamic_pointer_cast<const BranchNode>(current);
513  auto length = getCommonPrefixLength(current->key_nibbles, nibbles);
514  OUTCOME_TRY(n, retrieveChild(*parent_as_branch, nibbles[length]));
515  return getNode(n, nibbles.subspan(length + 1));
516  }
517 
518  case T::Leaf:
519  if (current->key_nibbles == nibbles) {
520  return current;
521  }
522  break;
523 
524  case T::LeafContainingHashes:
526 
527  case T::BranchContainingHashes:
529 
530  case T::Empty:
532 
533  case T::ReservedForCompactEncoding:
535 
536  default:
538  }
539  return nullptr;
540  }
541 
542  outcome::result<void> PolkadotTrieImpl::forNodeInPath(
543  ConstNodePtr parent,
544  const NibblesView &path,
545  const std::function<outcome::result<void>(BranchNode const &,
546  uint8_t idx)> &callback) const {
547  using T = TrieNode::Type;
548  if (parent == nullptr) {
549  return TrieError::NO_VALUE;
550  }
551 
552  const auto node_type = parent->getTrieType();
553  switch (node_type) {
554  case T::BranchEmptyValue:
555  case T::BranchWithValue: {
556  // path is completely covered by the parent key
557  if (parent->key_nibbles == path or path.empty()) {
558  return outcome::success();
559  }
560  auto common_length = getCommonPrefixLength(parent->key_nibbles, path);
561  auto common_nibbles =
562  gsl::make_span(parent->key_nibbles.data(), common_length);
563  // path is even less than the parent key (path is the prefix of the
564  // parent key)
565  if (path == common_nibbles
566  and path.size()
567  < static_cast<ssize_t>(parent->key_nibbles.size())) {
568  return outcome::success();
569  }
570  auto parent_as_branch =
571  std::dynamic_pointer_cast<const BranchNode>(parent);
572  OUTCOME_TRY(child,
573  retrieveChild(*parent_as_branch, path[common_length]));
574  OUTCOME_TRY(callback(*parent_as_branch, path[common_length]));
575  return forNodeInPath(child, path.subspan(common_length + 1), callback);
576  }
577 
578  case T::Leaf:
579  if (parent->key_nibbles == path) {
580  return outcome::success();
581  }
582  break;
583 
584  case T::LeafContainingHashes:
586 
587  case T::BranchContainingHashes:
589 
590  case T::Empty:
592 
593  case T::ReservedForCompactEncoding:
595 
596  default:
598  }
599  return TrieError::NO_VALUE;
600  }
601 
602  std::unique_ptr<PolkadotTrieCursor> PolkadotTrieImpl::trieCursor() {
603  return std::make_unique<PolkadotTrieCursorImpl>(shared_from_this());
604  }
605 
606  outcome::result<bool> PolkadotTrieImpl::contains(
607  const common::BufferView &key) const {
608  if (not nodes_->getRoot()) {
609  return false;
610  }
611 
612  OUTCOME_TRY(node,
613  getNode(nodes_->getRoot(), KeyNibbles::fromByteBuffer(key)));
614  return node != nullptr && node->value;
615  }
616 
617  bool PolkadotTrieImpl::empty() const {
618  return nodes_->getRoot() == nullptr;
619  }
620 
621  outcome::result<void> PolkadotTrieImpl::remove(
622  const common::BufferView &key) {
623  auto key_nibbles = KeyNibbles::fromByteBuffer(key);
624  // delete node will fetch nodes that it needs from the storage (the
625  // nodes typically are a path in the trie) and work on them in memory
626  auto root = nodes_->getRoot();
627  SL_TRACE(
628  logger_, "Remove by key {:l} (nibbles {})", key, key_nibbles.toHex());
629  OUTCOME_TRY(deleteNode(logger_, root, key_nibbles, *nodes_));
630  nodes_->setRoot(root);
631  return outcome::success();
632  }
633 
634  outcome::result<PolkadotTrie::ConstNodePtr> PolkadotTrieImpl::retrieveChild(
635  const BranchNode &parent, uint8_t idx) const {
636  OUTCOME_TRY(node, nodes_->getChild(parent, idx));
637  return std::move(node);
638  }
639 
640  outcome::result<PolkadotTrie::NodePtr> PolkadotTrieImpl::retrieveChild(
641  const BranchNode &parent, uint8_t idx) {
642  return nodes_->getChild(parent, idx);
643  }
644 
645 } // namespace kagome::storage::trie
outcome::result< std::tuple< bool, uint32_t > > clearPrefix(const common::BufferView &prefix, std::optional< uint64_t > limit, const OnDetachCallback &callback) override
outcome::result< void > remove(const common::BufferView &key) override
Remove value by key.
Class represents arbitrary (including empty) byte buffer.
Definition: buffer.hpp:29
uint16_t childrenBitmap() const
Definition: trie_node.cpp:15
std::array< std::shared_ptr< OpaqueTrieNode >, kMaxChildren > children
Definition: trie_node.hpp:141
outcome::result< ConstNodePtr > retrieveChild(const BranchNode &parent, uint8_t idx) const override
outcome::result< void > forNodeInPath(ConstNodePtr parent, const NibblesView &path, const std::function< outcome::result< void >(BranchNode const &, uint8_t idx)> &callback) const override
OUTCOME_CPP_DEFINE_CATEGORY(kagome::storage::trie, PolkadotTrieImpl::Error, e)
outcome::result< NodePtr > updateBranch(BranchPtr parent, const NibblesView &key_nibbles, const NodePtr &node)
OpaqueNodeStorage(PolkadotTrie::NodeRetrieveFunctor node_retriever, std::shared_ptr< TrieNode > root) noexcept
std::shared_ptr< TrieNode > NodePtr
PolkadotTrie::NodeRetrieveFunctor retrieve_node_
void setRoot(const std::shared_ptr< TrieNode > &root)
outcome::result< common::BufferConstRef > get(const common::BufferView &key) const override
Get value by key.
outcome::result< NodePtr > insert(const NodePtr &parent, const NibblesView &key_nibbles, NodePtr node)
outcome::result< std::shared_ptr< TrieNode > > getChild(BranchNode const &parent, uint8_t idx)
std::function< outcome::result< void >(const common::BufferView &key, std::optional< common::Buffer > &&value)> OnDetachCallback
gsl::span< const uint8_t > make_span(const rocksdb::Slice &s)
PolkadotTrieImpl(PolkadotTrie::NodeRetrieveFunctor f=PolkadotTrie::defaultNodeRetrieveFunctor)
SLBuffer< std::numeric_limits< size_t >::max()> Buffer
Definition: buffer.hpp:244
std::shared_ptr< BranchNode > BranchPtr
bool empty() const override
Returns true if the storage is empty.
std::shared_ptr< soralog::Logger > Logger
Definition: logger.hpp:23
std::function< outcome::result< NodePtr >(std::shared_ptr< OpaqueTrieNode > const &)> NodeRetrieveFunctor
std::shared_ptr< const TrieNode > ConstNodePtr
outcome::result< std::shared_ptr< const TrieNode > > getChild(BranchNode const &parent, uint8_t idx) const
outcome::result< std::optional< common::BufferConstRef > > tryGet(const common::BufferView &key) const override
Get value by key.
static outcome::result< std::unique_ptr< OpaqueNodeStorage > > createAt(std::shared_ptr< OpaqueTrieNode > root, PolkadotTrie::NodeRetrieveFunctor node_retriever)
outcome::result< bool > contains(const common::BufferView &key) const override
Checks if given key-value binding exists in the storage.
std::unique_ptr< PolkadotTrieCursor > trieCursor() override
std::unique_ptr< OpaqueNodeStorage > nodes_
outcome::result< NodePtr > getNode(ConstNodePtr parent, const NibblesView &key_nibbles) override
std::shared_ptr< const TrieNode > getRoot() const
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
static KeyNibbles fromByteBuffer(const common::BufferView &key)
Definition: trie_node.hpp:35
outcome::result< void > put(const common::BufferView &key, const common::Buffer &value) override
Store value by key.
const std::shared_ptr< TrieNode > & getRoot()
std::string toHex() const
Definition: buffer_view.hpp:30