Kagome
Polkadot Runtime Engine in C++17
polkadot_trie_cursor_impl.cpp
Go to the documentation of this file.
1 
7 
8 #include <utility>
9 
11 #include "macro/unreachable.hpp"
15 
18  e) {
20  switch (e) {
21  case E::INVALID_NODE_TYPE:
22  return "The processed node type is invalid";
23  case E::INVALID_CURSOR_ACCESS:
24  return "A trie cursor in an invalid state has been accessed (e.g. "
25  "next())";
26  case E::KEY_NOT_FOUND:
27  return "The requested key was not found";
28  }
29  return "Unknown TrieCursor error";
30 }
31 
32 namespace kagome::storage::trie {
33 
34  [[nodiscard]] outcome::result<void>
36  const TrieNode &child) {
37  auto *current_as_branch = dynamic_cast<const BranchNode *>(current_);
38  if (current_as_branch == nullptr) return Error::INVALID_NODE_TYPE;
39  path_.emplace_back(*current_as_branch, index);
40  current_ = &child;
41  return outcome::success();
42  }
43 
45  std::shared_ptr<const PolkadotTrie> trie)
46  : log_{log::createLogger("TrieCursor", "trie")},
47  trie_{std::move(trie)},
49  BOOST_ASSERT(trie_);
50  }
51 
52  outcome::result<std::unique_ptr<PolkadotTrieCursorImpl>>
54  const common::BufferView &key,
55  const std::shared_ptr<const PolkadotTrie> &trie) {
56  auto cursor = std::make_unique<PolkadotTrieCursorImpl>(trie);
57  OUTCOME_TRY(search_state, cursor->makeSearchStateAt(key));
58  cursor->state_ = std::move(search_state);
59  return cursor;
60  }
61 
62  outcome::result<bool> PolkadotTrieCursorImpl::seekFirst() {
65  return isValid();
66  }
67 
68  outcome::result<bool> PolkadotTrieCursorImpl::seek(
69  const common::BufferView &key) {
70  if (trie_->getRoot() == nullptr) {
72  return false;
73  }
74 
75  auto res = makeSearchStateAt(key);
76  if (res.has_error()) {
77  // if the given key is just not present in the trie, return false
78  state_ = InvalidState{res.error()};
79  if (res.error() == Error::KEY_NOT_FOUND) {
80  return false;
81  }
82  // on other errors - propagate them
83  return res.error();
84  }
85  state_ = std::move(res.value());
86  auto &current = std::get<SearchState>(state_).getCurrent();
87  // while there is a node in a trie with the given key, it contains no value,
88  // thus cannot be pointed at by the cursor
89  if (not current.value.has_value()) {
91  return false;
92  }
93 
94  return true;
95  }
96 
97  outcome::result<bool> PolkadotTrieCursorImpl::seekLast() {
98  auto *current = trie_->getRoot().get();
99  if (current == nullptr) {
101  return false;
102  }
103  state_ = SearchState{*current};
104  auto &search_state = std::get<SearchState>(state_);
105 
106  // find the rightmost leaf
107  while (current->getTrieType() != NodeType::Leaf) {
108  auto type = current->getTrieType();
109  if (type == NodeType::BranchEmptyValue
110  or type == NodeType::BranchWithValue) {
111  auto &branch = dynamic_cast<const BranchNode &>(*current);
112  // find the rightmost child
113  for (int8_t i = BranchNode::kMaxChildren - 1; i >= 0; i--) {
114  if (branch.children.at(i) != nullptr) {
115  SAFE_CALL(child, trie_->retrieveChild(branch, i))
116  SAFE_VOID_CALL(search_state.visitChild(i, *child))
117  current = &search_state.getCurrent();
118  break;
119  }
120  }
121 
122  } else {
123  // supposed to be unreachable
125  }
126  }
127  return true;
128  }
129 
131  const TrieNode &current, gsl::span<const uint8_t> sought_nibbles) {
132  BOOST_ASSERT(isValid());
133  auto [sought_nibbles_mismatch, current_mismatch] =
134  std::mismatch(sought_nibbles.begin(),
135  sought_nibbles.end(),
136  current.key_nibbles.begin(),
137  current.key_nibbles.end());
138  // one nibble sequence is a prefix of the other
139  bool sought_is_prefix = sought_nibbles_mismatch == sought_nibbles.end();
140  bool current_is_prefix = current_mismatch == current.key_nibbles.end();
141 
142  // if sought nibbles are lexicographically less or equal to the current
143  // nibbles, we just take the closest node with value
144  bool sought_less_or_eq =
145  sought_is_prefix
146  or (not current_is_prefix
147  and *sought_nibbles_mismatch < *current_mismatch);
148  SL_TRACE(log_,
149  "The sought key '{}' is {} than current '{}'",
150  common::hex_lower(sought_nibbles),
151  sought_less_or_eq ? "less or eq" : "greater",
152  common::hex_lower(current.key_nibbles));
153  if (sought_less_or_eq) {
154  switch (current.getTrieType()) {
157  SL_TRACE(log_, "We're in a branch and search next node in subtree");
159  return outcome::success();
160  }
161  case NodeType::Leaf:
162  SL_TRACE(log_, "We're in a leaf {} and done", key().value());
163  return outcome::success();
164  default:
166  }
167  }
168  // if the left part of the sought key exceeds the current node's key,
169  // but its prefix is equal to the key, then we proceed to a child node
170  // that starts with a nibble that is greater of equal to the first
171  // nibble of the left part (if there is no such child, proceed to the
172  // next case)
173  bool sought_is_longer = current_is_prefix and not sought_is_prefix;
174  if (sought_is_longer) {
175  switch (current.getTrieType()) {
178  auto mismatch_pos = sought_nibbles_mismatch - sought_nibbles.begin();
179  auto &branch = dynamic_cast<const BranchNode &>(current);
180  SAFE_CALL(child,
181  visitChildWithMinIdx(branch, sought_nibbles[mismatch_pos]))
182  if (child) {
183  uint8_t child_idx =
184  std::get<SearchState>(state_).getPath().back().child_idx;
185  SL_TRACE(log_,
186  "We're in a branch and proceed to child {:x}",
187  (int)child_idx);
188  if (child_idx > sought_nibbles[mismatch_pos]) {
189  return nextNodeWithValueInSubTree(*child);
190  } else {
191  return seekLowerBoundInternal(
192  *child, sought_nibbles.subspan(mismatch_pos + 1));
193  }
194  }
195  break; // go to case3
196  }
197  case NodeType::Leaf: {
198  break; // go to case3
199  }
200  default:
202  }
203  }
204  // if the left part of the key is longer than the current or
205  // lexicographically greater than the current, we must return to its
206  // parent and find a child greater than the current one
207  bool longer_or_greater =
208  sought_is_longer
209  or (not(sought_is_prefix or current_is_prefix)
210  and *sought_nibbles_mismatch > *current_mismatch);
211  if (longer_or_greater) {
212  SL_TRACE(log_, "We're looking for next node with value in outer tree");
214  if (!found) {
216  }
217  SL_TRACE(log_, "Done at {}", key().value());
218  return outcome::success();
219  }
221  }
222 
224  const common::BufferView &key) {
225  if (trie_->getRoot() == nullptr) {
226  SL_TRACE(log_, "Seek lower bound for {} -> null root", key);
228  return outcome::success();
229  }
230  state_ = SearchState{*trie_->getRoot()};
231  auto nibbles = KeyNibbles::fromByteBuffer(key);
232  SAFE_VOID_CALL(seekLowerBoundInternal(*trie_->getRoot(), nibbles))
233  return outcome::success();
234  }
235 
237  BOOST_ASSERT(std::holds_alternative<SearchState>(state_));
238  auto &search_state = std::get<SearchState>(state_);
239 
240  const auto &search_path = search_state.getPath();
241  while (not search_path.empty()) {
242  auto [parent, idx] = search_path.back();
243  BOOST_VERIFY_MSG(search_state.leaveChild(),
244  "Guaranteed by the loop condition");
245  SAFE_CALL(child, visitChildWithMinIdx(parent, idx + 1))
246  if (child != nullptr) {
247  SL_TRACE(log_,
248  "A greater child exists (idx {}), proceed to it",
249  search_path.back().child_idx);
251  return true;
252  }
253  }
254  return false;
255  }
256 
258  const TrieNode &parent) {
259  auto *current = &parent;
260  while (not current->value.has_value()) {
261  if (not current->isBranch()) {
263  }
264  SAFE_CALL(child, visitChildWithMinIdx(*current))
265  SL_TRACE(log_,
266  "Proceed to child {:x}",
267  (int)std::get<SearchState>(state_).getPath().back().child_idx);
268  BOOST_ASSERT_MSG(child != nullptr, "Branch node must contain a leaf");
269  current = child;
270  }
271  return outcome::success();
272  }
273 
275  const common::BufferView &sought_key) {
276  SL_TRACE(log_, "Seek upper bound for {}", sought_key);
277  SAFE_VOID_CALL(seekLowerBound(sought_key))
278  if (auto key = this->key(); key.has_value() && key.value() == sought_key) {
280  }
281  return outcome::success();
282  }
283 
284  outcome::result<const TrieNode *>
286  uint8_t min_idx) {
287  BOOST_ASSERT(std::holds_alternative<SearchState>(state_));
288  auto &search_state = std::get<SearchState>(state_);
289  for (uint8_t i = min_idx; i < BranchNode::kMaxChildren; i++) {
290  auto &branch = dynamic_cast<const BranchNode &>(parent);
291  if (branch.children.at(i)) {
292  OUTCOME_TRY(child, trie_->retrieveChild(branch, i));
293  BOOST_ASSERT(child != nullptr);
294  OUTCOME_TRY(search_state.visitChild(i, *child));
295  return child.get();
296  }
297  }
298  return nullptr;
299  }
300 
302  return std::holds_alternative<SearchState>(state_);
303  }
304 
305  outcome::result<void> PolkadotTrieCursorImpl::next() {
306  if (std::holds_alternative<InvalidState>(state_)) {
308  }
309 
310  if (trie_->getRoot() == nullptr) {
311  return outcome::success();
312  }
313 
314  if (key().has_value()) {
315  SL_TRACE(log_, "Searching next key, current is {}", key().value());
316  } else {
317  SL_TRACE(log_, "Searching next key, no current key");
318  }
319 
320  if (std::holds_alternative<UninitializedState>(state_)) {
321  state_ = SearchState{*trie_->getRoot()};
322  if (trie_->getRoot()->value) {
323  return outcome::success();
324  }
325  }
326  BOOST_ASSERT(std::holds_alternative<SearchState>(state_));
327  auto &search_state = std::get<SearchState>(state_);
328 
329  // if we're in a branch, means nodes in its subtree are not visited yet
330  if (search_state.getCurrent().isBranch()) {
331  SL_TRACE(log_, "We're in a branch and looking for next value in subtree");
332  SAFE_CALL(child, visitChildWithMinIdx(search_state.getCurrent()))
333  BOOST_ASSERT_MSG(child != nullptr,
334  "Since parent is branch, there must be a child");
335  SL_TRACE(log_, "Go to child {}", search_state.getPath().back().child_idx);
337  SL_TRACE(log_, "Found {}", key().value());
338  return outcome::success();
339  }
340  // we're in a leaf, should go up the tree and search there
341  SL_TRACE(log_, "We're in a leaf and looking for next value in outer tree");
343  if (not found) {
344  SL_TRACE(log_, "Not found anything");
346  } else {
347  SL_TRACE(log_, "Found {}", key().value());
348  }
349  return outcome::success();
350  }
351 
353  BOOST_ASSERT(std::holds_alternative<SearchState>(state_));
354  const auto &search_state = std::get<SearchState>(state_);
355  KeyNibbles key_nibbles;
356  for (const auto &node_idx : search_state.getPath()) {
357  const auto &node = node_idx.parent;
358  auto idx = node_idx.child_idx;
359  std::copy(node.key_nibbles.begin(),
360  node.key_nibbles.end(),
361  std::back_inserter<Buffer>(key_nibbles));
362  key_nibbles.putUint8(idx);
363  }
364  key_nibbles.put(search_state.getCurrent().key_nibbles);
365  return key_nibbles.toByteBuffer();
366  }
367 
368  std::optional<common::Buffer> PolkadotTrieCursorImpl::key() const {
369  if (const auto *search_state = std::get_if<SearchState>(&state_);
370  search_state != nullptr) {
371  return collectKey();
372  }
373  return std::nullopt;
374  }
375 
376  std::optional<common::BufferConstRef> PolkadotTrieCursorImpl::value() const {
377  if (const auto *search_state = std::get_if<SearchState>(&state_);
378  search_state != nullptr) {
379  const auto &value_opt = search_state->getCurrent().value;
380  if (value_opt) {
381  return std::make_optional<common::BufferConstRef>(
382  std::cref(value_opt.value()));
383  }
384  return std::nullopt;
385  }
386  return std::nullopt;
387  }
388 
390  -> outcome::result<SearchState> {
391  if (trie_->getRoot() == nullptr) {
392  return Error::KEY_NOT_FOUND;
393  }
394  SearchState search_state{*trie_->getRoot()};
395 
396  auto add_visited_child = [&search_state, this](
397  auto &branch,
398  auto idx) mutable -> outcome::result<void> {
399  OUTCOME_TRY(child, trie_->retrieveChild(branch, idx));
400  BOOST_VERIFY( // NOLINT(bugprone-lambda-function-name)
401  search_state.visitChild(idx, *child));
402  return outcome::success();
403  };
404  auto res = trie_->forNodeInPath(
405  trie_->getRoot(), KeyNibbles::fromByteBuffer(key), add_visited_child);
406  if (res.has_error()) {
407  if (res.error() == TrieError::NO_VALUE) {
408  return Error::KEY_NOT_FOUND;
409  }
410  return res.error();
411  }
412  return search_state;
413  }
414 
415 } // namespace kagome::storage::trie
Class represents arbitrary (including empty) byte buffer.
Definition: buffer.hpp:29
outcome::result< void > seekLowerBoundInternal(const TrieNode &current, gsl::span< const uint8_t > left_nibbles)
outcome::result< bool > seekFirst() override
Same as std::begin(...);.
auto makeSearchStateAt(const common::BufferView &key) -> outcome::result< SearchState >
#define SAFE_CALL(res, expr)
std::string hex_lower(const gsl::span< const uint8_t > bytes) noexcept
Converts bytes to hex representation.
Definition: hexutil.cpp:52
outcome::result< void > nextNodeWithValueInSubTree(const TrieNode &subtree_root)
outcome::result< void > seekLowerBound(const common::BufferView &key) override
static constexpr uint8_t kMaxChildren
Definition: trie_node.hpp:124
#define SAFE_VOID_CALL(expr)
outcome::result< bool > seek(const common::BufferView &key) override
Find given key and seek iterator to this key.
#define UNREACHABLE
Definition: unreachable.hpp:21
outcome::result< void > seekUpperBound(const common::BufferView &key) override
outcome::result< bool > seekLast() override
Same as std::rbegin(...);, e.g. points to the last valid element.
outcome::result< void > visitChild(uint8_t index, const TrieNode &child)
PolkadotTrieCursorImpl(std::shared_ptr< const PolkadotTrie > trie)
bool isValid() const override
Is the cursor in a valid state?
Type getTrieType() const noexcept
Definition: trie_node.hpp:109
outcome::result< const TrieNode * > visitChildWithMinIdx(const TrieNode &node, uint8_t min_idx=0)
OUTCOME_CPP_DEFINE_CATEGORY(kagome::storage::trie, PolkadotTrieCursorImpl::Error, e)
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
std::optional< common::BufferConstRef > value() const override
Getter for value of the element currently pointed at.
static KeyNibbles fromByteBuffer(const common::BufferView &key)
Definition: trie_node.hpp:35
static outcome::result< std::unique_ptr< PolkadotTrieCursorImpl > > createAt(const common::BufferView &key, const std::shared_ptr< const PolkadotTrie > &trie)
outcome::result< void > next() override
Make step forward.
std::optional< common::Buffer > key() const override
Getter for the key of the element currently pointed at.